Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / builds / ext-all-sandbox-debug.js
1 /*
2 Ext JS - JavaScript Library
3 Copyright (c) 2006-2011, Sencha Inc.
4 All rights reserved.
5 licensing@sencha.com
6 */
7 (function(Ext){
8 if (typeof Ext === 'undefined') {
9 this.Ext = {};
10 }
11
12 Ext.buildSettings = {"baseCSSPrefix":"x4-","scopeResetCSS":true};
13 /*
14 Ext JS - JavaScript Library
15 Copyright (c) 2006-2011, Sencha Inc.
16 All rights reserved.
17 licensing@sencha.com
18 */
19 /**
20  * @class Ext
21  * @singleton
22  */
23 (function() {
24     var global = this,
25         objectPrototype = Object.prototype,
26         toString = Object.prototype.toString,
27         enumerables = true,
28         enumerablesTest = { toString: 1 },
29         i;
30
31     if (typeof Ext === 'undefined') {
32         global.Ext = {};
33     }
34
35     Ext.global = global;
36
37     for (i in enumerablesTest) {
38         enumerables = null;
39     }
40
41     if (enumerables) {
42         enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
43                        'toLocaleString', 'toString', 'constructor'];
44     }
45
46     /**
47      * An array containing extra enumerables for old browsers
48      * @type Array
49      */
50     Ext.enumerables = enumerables;
51
52     /**
53      * Copies all the properties of config to the specified object.
54      * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
55      * {@link Ext.Object#merge} instead.
56      * @param {Object} object The receiver of the properties
57      * @param {Object} config The source of the properties
58      * @param {Object} defaults A different object that will also be applied for default values
59      * @return {Object} returns obj
60      */
61     Ext.apply = function(object, config, defaults) {
62         if (defaults) {
63             Ext.apply(object, defaults);
64         }
65
66         if (object && config && typeof config === 'object') {
67             var i, j, k;
68
69             for (i in config) {
70                 object[i] = config[i];
71             }
72
73             if (enumerables) {
74                 for (j = enumerables.length; j--;) {
75                     k = enumerables[j];
76                     if (config.hasOwnProperty(k)) {
77                         object[k] = config[k];
78                     }
79                 }
80             }
81         }
82
83         return object;
84     };
85
86     Ext.buildSettings = Ext.apply({
87         baseCSSPrefix: 'x-',
88         scopeResetCSS: false
89     }, Ext.buildSettings || {});
90
91     Ext.apply(Ext, {
92         /**
93          * A reusable empty function
94          */
95         emptyFn: function() {},
96
97         baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
98
99         /**
100          * Copies all the properties of config to object if they don't already exist.
101          * @function
102          * @param {Object} object The receiver of the properties
103          * @param {Object} config The source of the properties
104          * @return {Object} returns obj
105          */
106         applyIf: function(object, config) {
107             var property;
108
109             if (object) {
110                 for (property in config) {
111                     if (object[property] === undefined) {
112                         object[property] = config[property];
113                     }
114                 }
115             }
116
117             return object;
118         },
119
120         /**
121          * Iterates either an array or an object. This method delegates to
122          * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
123          *
124          * @param {Object/Array} object The object or array to be iterated.
125          * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
126          * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
127          * type that is being iterated.
128          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
129          * Defaults to the object being iterated itself.
130          * @markdown
131          */
132         iterate: function(object, fn, scope) {
133             if (Ext.isEmpty(object)) {
134                 return;
135             }
136
137             if (scope === undefined) {
138                 scope = object;
139             }
140
141             if (Ext.isIterable(object)) {
142                 Ext.Array.each.call(Ext.Array, object, fn, scope);
143             }
144             else {
145                 Ext.Object.each.call(Ext.Object, object, fn, scope);
146             }
147         }
148     });
149
150     Ext.apply(Ext, {
151
152         /**
153          * This method deprecated. Use {@link Ext#define Ext.define} instead.
154          * @method
155          * @param {Function} superclass
156          * @param {Object} overrides
157          * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
158          * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
159          */
160         extend: function() {
161             // inline overrides
162             var objectConstructor = objectPrototype.constructor,
163                 inlineOverrides = function(o) {
164                 for (var m in o) {
165                     if (!o.hasOwnProperty(m)) {
166                         continue;
167                     }
168                     this[m] = o[m];
169                 }
170             };
171
172             return function(subclass, superclass, overrides) {
173                 // First we check if the user passed in just the superClass with overrides
174                 if (Ext.isObject(superclass)) {
175                     overrides = superclass;
176                     superclass = subclass;
177                     subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
178                         superclass.apply(this, arguments);
179                     };
180                 }
181
182                 if (!superclass) {
183                     Ext.Error.raise({
184                         sourceClass: 'Ext',
185                         sourceMethod: 'extend',
186                         msg: 'Attempting to extend from a class which has not been loaded on the page.'
187                     });
188                 }
189
190                 // We create a new temporary class
191                 var F = function() {},
192                     subclassProto, superclassProto = superclass.prototype;
193
194                 F.prototype = superclassProto;
195                 subclassProto = subclass.prototype = new F();
196                 subclassProto.constructor = subclass;
197                 subclass.superclass = superclassProto;
198
199                 if (superclassProto.constructor === objectConstructor) {
200                     superclassProto.constructor = superclass;
201                 }
202
203                 subclass.override = function(overrides) {
204                     Ext.override(subclass, overrides);
205                 };
206
207                 subclassProto.override = inlineOverrides;
208                 subclassProto.proto = subclassProto;
209
210                 subclass.override(overrides);
211                 subclass.extend = function(o) {
212                     return Ext.extend(subclass, o);
213                 };
214
215                 return subclass;
216             };
217         }(),
218
219         /**
220          * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
221
222     Ext.define('My.cool.Class', {
223         sayHi: function() {
224             alert('Hi!');
225         }
226     }
227
228     Ext.override(My.cool.Class, {
229         sayHi: function() {
230             alert('About to say...');
231
232             this.callOverridden();
233         }
234     });
235
236     var cool = new My.cool.Class();
237     cool.sayHi(); // alerts 'About to say...'
238                   // alerts 'Hi!'
239
240          * Please note that `this.callOverridden()` only works if the class was previously
241          * created with {@link Ext#define)
242          *
243          * @param {Object} cls The class to override
244          * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
245          * containing one or more methods.
246          * @method override
247          * @markdown
248          */
249         override: function(cls, overrides) {
250             if (cls.prototype.$className) {
251                 return cls.override(overrides);
252             }
253             else {
254                 Ext.apply(cls.prototype, overrides);
255             }
256         }
257     });
258
259     // A full set of static methods to do type checking
260     Ext.apply(Ext, {
261
262         /**
263          * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
264          * value (second argument) otherwise.
265          *
266          * @param {Mixed} value The value to test
267          * @param {Mixed} defaultValue The value to return if the original value is empty
268          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
269          * @return {Mixed} value, if non-empty, else defaultValue
270          */
271         valueFrom: function(value, defaultValue, allowBlank){
272             return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
273         },
274
275         /**
276          * Returns the type of the given variable in string format. List of possible values are:
277          *
278          * - `undefined`: If the given value is `undefined`
279          * - `null`: If the given value is `null`
280          * - `string`: If the given value is a string
281          * - `number`: If the given value is a number
282          * - `boolean`: If the given value is a boolean value
283          * - `date`: If the given value is a `Date` object
284          * - `function`: If the given value is a function reference
285          * - `object`: If the given value is an object
286          * - `array`: If the given value is an array
287          * - `regexp`: If the given value is a regular expression
288          * - `element`: If the given value is a DOM Element
289          * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
290          * - `whitespace`: If the given value is a DOM text node and contains only whitespace
291          *
292          * @param {Mixed} value
293          * @return {String}
294          * @markdown
295          */
296         typeOf: function(value) {
297             if (value === null) {
298                 return 'null';
299             }
300
301             var type = typeof value;
302
303             if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
304                 return type;
305             }
306
307             var typeToString = toString.call(value);
308
309             switch(typeToString) {
310                 case '[object Array]':
311                     return 'array';
312                 case '[object Date]':
313                     return 'date';
314                 case '[object Boolean]':
315                     return 'boolean';
316                 case '[object Number]':
317                     return 'number';
318                 case '[object RegExp]':
319                     return 'regexp';
320             }
321
322             if (type === 'function') {
323                 return 'function';
324             }
325
326             if (type === 'object') {
327                 if (value.nodeType !== undefined) {
328                     if (value.nodeType === 3) {
329                         return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
330                     }
331                     else {
332                         return 'element';
333                     }
334                 }
335
336                 return 'object';
337             }
338
339             Ext.Error.raise({
340                 sourceClass: 'Ext',
341                 sourceMethod: 'typeOf',
342                 msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
343             });
344         },
345
346         /**
347          * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
348          *
349          * - `null`
350          * - `undefined`
351          * - a zero-length array
352          * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
353          *
354          * @param {Mixed} value The value to test
355          * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
356          * @return {Boolean}
357          * @markdown
358          */
359         isEmpty: function(value, allowEmptyString) {
360             return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
361         },
362
363         /**
364          * Returns true if the passed value is a JavaScript Array, false otherwise.
365          *
366          * @param {Mixed} target The target to test
367          * @return {Boolean}
368          * @method
369          */
370         isArray: ('isArray' in Array) ? Array.isArray : function(value) {
371             return toString.call(value) === '[object Array]';
372         },
373
374         /**
375          * Returns true if the passed value is a JavaScript Date object, false otherwise.
376          * @param {Object} object The object to test
377          * @return {Boolean}
378          */
379         isDate: function(value) {
380             return toString.call(value) === '[object Date]';
381         },
382
383         /**
384          * Returns true if the passed value is a JavaScript Object, false otherwise.
385          * @param {Mixed} value The value to test
386          * @return {Boolean}
387          * @method
388          */
389         isObject: (toString.call(null) === '[object Object]') ?
390         function(value) {
391             return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.nodeType === undefined;
392         } :
393         function(value) {
394             return toString.call(value) === '[object Object]';
395         },
396
397         /**
398          * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
399          * @param {Mixed} value The value to test
400          * @return {Boolean}
401          */
402         isPrimitive: function(value) {
403             var type = typeof value;
404
405             return type === 'string' || type === 'number' || type === 'boolean';
406         },
407
408         /**
409          * Returns true if the passed value is a JavaScript Function, false otherwise.
410          * @param {Mixed} value The value to test
411          * @return {Boolean}
412          * @method
413          */
414         isFunction:
415         // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
416         // Object.prorotype.toString (slower)
417         (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
418             return toString.call(value) === '[object Function]';
419         } : function(value) {
420             return typeof value === 'function';
421         },
422
423         /**
424          * Returns true if the passed value is a number. Returns false for non-finite numbers.
425          * @param {Mixed} value The value to test
426          * @return {Boolean}
427          */
428         isNumber: function(value) {
429             return typeof value === 'number' && isFinite(value);
430         },
431
432         /**
433          * Validates that a value is numeric.
434          * @param {Mixed} value Examples: 1, '1', '2.34'
435          * @return {Boolean} True if numeric, false otherwise
436          */
437         isNumeric: function(value) {
438             return !isNaN(parseFloat(value)) && isFinite(value);
439         },
440
441         /**
442          * Returns true if the passed value is a string.
443          * @param {Mixed} value The value to test
444          * @return {Boolean}
445          */
446         isString: function(value) {
447             return typeof value === 'string';
448         },
449
450         /**
451          * Returns true if the passed value is a boolean.
452          *
453          * @param {Mixed} value The value to test
454          * @return {Boolean}
455          */
456         isBoolean: function(value) {
457             return typeof value === 'boolean';
458         },
459
460         /**
461          * Returns true if the passed value is an HTMLElement
462          * @param {Mixed} value The value to test
463          * @return {Boolean}
464          */
465         isElement: function(value) {
466             return value ? value.nodeType === 1 : false;
467         },
468
469         /**
470          * Returns true if the passed value is a TextNode
471          * @param {Mixed} value The value to test
472          * @return {Boolean}
473          */
474         isTextNode: function(value) {
475             return value ? value.nodeName === "#text" : false;
476         },
477
478         /**
479          * Returns true if the passed value is defined.
480          * @param {Mixed} value The value to test
481          * @return {Boolean}
482          */
483         isDefined: function(value) {
484             return typeof value !== 'undefined';
485         },
486
487         /**
488          * Returns true if the passed value is iterable, false otherwise
489          * @param {Mixed} value The value to test
490          * @return {Boolean}
491          */
492         isIterable: function(value) {
493             return (value && typeof value !== 'string') ? value.length !== undefined : false;
494         }
495     });
496
497     Ext.apply(Ext, {
498
499         /**
500          * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
501          * @param {Mixed} item The variable to clone
502          * @return {Mixed} clone
503          */
504         clone: function(item) {
505             if (item === null || item === undefined) {
506                 return item;
507             }
508
509             // DOM nodes
510             // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
511             // recursively
512             if (item.nodeType && item.cloneNode) {
513                 return item.cloneNode(true);
514             }
515
516             var type = toString.call(item);
517
518             // Date
519             if (type === '[object Date]') {
520                 return new Date(item.getTime());
521             }
522
523             var i, j, k, clone, key;
524
525             // Array
526             if (type === '[object Array]') {
527                 i = item.length;
528
529                 clone = [];
530
531                 while (i--) {
532                     clone[i] = Ext.clone(item[i]);
533                 }
534             }
535             // Object
536             else if (type === '[object Object]' && item.constructor === Object) {
537                 clone = {};
538
539                 for (key in item) {
540                     clone[key] = Ext.clone(item[key]);
541                 }
542
543                 if (enumerables) {
544                     for (j = enumerables.length; j--;) {
545                         k = enumerables[j];
546                         clone[k] = item[k];
547                     }
548                 }
549             }
550
551             return clone || item;
552         },
553
554         /**
555          * @private
556          * Generate a unique reference of Ext in the global scope, useful for sandboxing
557          */
558         getUniqueGlobalNamespace: function() {
559             var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
560
561             if (uniqueGlobalNamespace === undefined) {
562                 var i = 0;
563
564                 do {
565                     uniqueGlobalNamespace = 'ExtSandbox' + (++i);
566                 } while (Ext.global[uniqueGlobalNamespace] !== undefined);
567
568                 Ext.global[uniqueGlobalNamespace] = Ext;
569                 this.uniqueGlobalNamespace = uniqueGlobalNamespace;
570             }
571
572             return uniqueGlobalNamespace;
573         },
574
575         /**
576          * @private
577          */
578         functionFactory: function() {
579             var args = Array.prototype.slice.call(arguments);
580
581             if (args.length > 0) {
582                 args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
583                     args[args.length - 1];
584             }
585
586             return Function.prototype.constructor.apply(Function.prototype, args);
587         }
588     });
589
590     /**
591      * Old alias to {@link Ext#typeOf}
592      * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
593      * @method
594      */
595     Ext.type = Ext.typeOf;
596
597 })();
598
599 /**
600  * @author Jacky Nguyen <jacky@sencha.com>
601  * @docauthor Jacky Nguyen <jacky@sencha.com>
602  * @class Ext.Version
603  *
604  * A utility class that wrap around a string version number and provide convenient
605  * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
606
607     var version = new Ext.Version('1.0.2beta');
608     console.log("Version is " + version); // Version is 1.0.2beta
609
610     console.log(version.getMajor()); // 1
611     console.log(version.getMinor()); // 0
612     console.log(version.getPatch()); // 2
613     console.log(version.getBuild()); // 0
614     console.log(version.getRelease()); // beta
615
616     console.log(version.isGreaterThan('1.0.1')); // True
617     console.log(version.isGreaterThan('1.0.2alpha')); // True
618     console.log(version.isGreaterThan('1.0.2RC')); // False
619     console.log(version.isGreaterThan('1.0.2')); // False
620     console.log(version.isLessThan('1.0.2')); // True
621
622     console.log(version.match(1.0)); // True
623     console.log(version.match('1.0.2')); // True
624
625  * @markdown
626  */
627 (function() {
628
629 // Current core version
630 var version = '4.0.1', Version;
631     Ext.Version = Version = Ext.extend(Object, {
632
633         /**
634          * @constructor
635          * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
636          * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
637          * @return {Ext.Version} this
638          * @param version
639          */
640         constructor: function(version) {
641             var parts, releaseStartIndex;
642
643             if (version instanceof Version) {
644                 return version;
645             }
646
647             this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
648
649             releaseStartIndex = this.version.search(/([^\d\.])/);
650
651             if (releaseStartIndex !== -1) {
652                 this.release = this.version.substr(releaseStartIndex, version.length);
653                 this.shortVersion = this.version.substr(0, releaseStartIndex);
654             }
655
656             this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
657
658             parts = this.version.split('.');
659
660             this.major = parseInt(parts.shift() || 0, 10);
661             this.minor = parseInt(parts.shift() || 0, 10);
662             this.patch = parseInt(parts.shift() || 0, 10);
663             this.build = parseInt(parts.shift() || 0, 10);
664
665             return this;
666         },
667
668         /**
669          * Override the native toString method
670          * @private
671          * @return {String} version
672          */
673         toString: function() {
674             return this.version;
675         },
676
677         /**
678          * Override the native valueOf method
679          * @private
680          * @return {String} version
681          */
682         valueOf: function() {
683             return this.version;
684         },
685
686         /**
687          * Returns the major component value
688          * @return {Number} major
689          */
690         getMajor: function() {
691             return this.major || 0;
692         },
693
694         /**
695          * Returns the minor component value
696          * @return {Number} minor
697          */
698         getMinor: function() {
699             return this.minor || 0;
700         },
701
702         /**
703          * Returns the patch component value
704          * @return {Number} patch
705          */
706         getPatch: function() {
707             return this.patch || 0;
708         },
709
710         /**
711          * Returns the build component value
712          * @return {Number} build
713          */
714         getBuild: function() {
715             return this.build || 0;
716         },
717
718         /**
719          * Returns the release component value
720          * @return {Number} release
721          */
722         getRelease: function() {
723             return this.release || '';
724         },
725
726         /**
727          * Returns whether this version if greater than the supplied argument
728          * @param {String/Number} target The version to compare with
729          * @return {Boolean} True if this version if greater than the target, false otherwise
730          */
731         isGreaterThan: function(target) {
732             return Version.compare(this.version, target) === 1;
733         },
734
735         /**
736          * Returns whether this version if smaller than the supplied argument
737          * @param {String/Number} target The version to compare with
738          * @return {Boolean} True if this version if smaller than the target, false otherwise
739          */
740         isLessThan: function(target) {
741             return Version.compare(this.version, target) === -1;
742         },
743
744         /**
745          * Returns whether this version equals to the supplied argument
746          * @param {String/Number} target The version to compare with
747          * @return {Boolean} True if this version equals to the target, false otherwise
748          */
749         equals: function(target) {
750             return Version.compare(this.version, target) === 0;
751         },
752
753         /**
754          * Returns whether this version matches the supplied argument. Example:
755          * <pre><code>
756          * var version = new Ext.Version('1.0.2beta');
757          * console.log(version.match(1)); // True
758          * console.log(version.match(1.0)); // True
759          * console.log(version.match('1.0.2')); // True
760          * console.log(version.match('1.0.2RC')); // False
761          * </code></pre>
762          * @param {String/Number} target The version to compare with
763          * @return {Boolean} True if this version matches the target, false otherwise
764          */
765         match: function(target) {
766             target = String(target);
767             return this.version.substr(0, target.length) === target;
768         },
769
770         /**
771          * Returns this format: [major, minor, patch, build, release]. Useful for comparison
772          * @return {Array}
773          */
774         toArray: function() {
775             return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
776         },
777
778         /**
779          * Returns shortVersion version without dots and release
780          * @return {String}
781          */
782         getShortVersion: function() {
783             return this.shortVersion;
784         }
785     });
786
787     Ext.apply(Version, {
788         // @private
789         releaseValueMap: {
790             'dev': -6,
791             'alpha': -5,
792             'a': -5,
793             'beta': -4,
794             'b': -4,
795             'rc': -3,
796             '#': -2,
797             'p': -1,
798             'pl': -1
799         },
800
801         /**
802          * Converts a version component to a comparable value
803          *
804          * @static
805          * @param {Mixed} value The value to convert
806          * @return {Mixed}
807          */
808         getComponentValue: function(value) {
809             return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
810         },
811
812         /**
813          * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
814          * they are handled in the following order:
815          * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
816          *
817          * @static
818          * @param {String} current The current version to compare to
819          * @param {String} target The target version to compare to
820          * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent
821          */
822         compare: function(current, target) {
823             var currentValue, targetValue, i;
824
825             current = new Version(current).toArray();
826             target = new Version(target).toArray();
827
828             for (i = 0; i < Math.max(current.length, target.length); i++) {
829                 currentValue = this.getComponentValue(current[i]);
830                 targetValue = this.getComponentValue(target[i]);
831
832                 if (currentValue < targetValue) {
833                     return -1;
834                 } else if (currentValue > targetValue) {
835                     return 1;
836                 }
837             }
838
839             return 0;
840         }
841     });
842
843     Ext.apply(Ext, {
844         /**
845          * @private
846          */
847         versions: {},
848
849         /**
850          * @private
851          */
852         lastRegisteredVersion: null,
853
854         /**
855          * Set version number for the given package name.
856          *
857          * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'
858          * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'
859          * @return {Ext}
860          */
861         setVersion: function(packageName, version) {
862             Ext.versions[packageName] = new Version(version);
863             Ext.lastRegisteredVersion = Ext.versions[packageName];
864
865             return this;
866         },
867
868         /**
869          * Get the version number of the supplied package name; will return the last registered version
870          * (last Ext.setVersion call) if there's no package name given.
871          *
872          * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'
873          * @return {Ext.Version} The version
874          */
875         getVersion: function(packageName) {
876             if (packageName === undefined) {
877                 return Ext.lastRegisteredVersion;
878             }
879
880             return Ext.versions[packageName];
881         },
882
883         /**
884          * Create a closure for deprecated code.
885          *
886     // This means Ext.oldMethod is only supported in 4.0.0beta and older.
887     // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
888     // the closure will not be invoked
889     Ext.deprecate('extjs', '4.0.0beta', function() {
890         Ext.oldMethod = Ext.newMethod;
891
892         ...
893     });
894
895          * @param {String} packageName The package name
896          * @param {String} since The last version before it's deprecated
897          * @param {Function} closure The callback function to be executed with the specified version is less than the current version
898          * @param {Object} scope The execution scope (<tt>this</tt>) if the closure
899          * @markdown
900          */
901         deprecate: function(packageName, since, closure, scope) {
902             if (Version.compare(Ext.getVersion(packageName), since) < 1) {
903                 closure.call(scope);
904             }
905         }
906     }); // End Versioning
907
908     Ext.setVersion('core', version);
909
910 })();
911
912 /**
913  * @class Ext.String
914  *
915  * A collection of useful static methods to deal with strings
916  * @singleton
917  */
918
919 Ext.String = {
920     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,
921     escapeRe: /('|\\)/g,
922     formatRe: /\{(\d+)\}/g,
923     escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
924
925     /**
926      * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
927      * @param {String} value The string to encode
928      * @return {String} The encoded text
929      * @method
930      */
931     htmlEncode: (function() {
932         var entities = {
933             '&': '&amp;',
934             '>': '&gt;',
935             '<': '&lt;',
936             '"': '&quot;'
937         }, keys = [], p, regex;
938         
939         for (p in entities) {
940             keys.push(p);
941         }
942         
943         regex = new RegExp('(' + keys.join('|') + ')', 'g');
944         
945         return function(value) {
946             return (!value) ? value : String(value).replace(regex, function(match, capture) {
947                 return entities[capture];    
948             });
949         };
950     })(),
951
952     /**
953      * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
954      * @param {String} value The string to decode
955      * @return {String} The decoded text
956      * @method
957      */
958     htmlDecode: (function() {
959         var entities = {
960             '&amp;': '&',
961             '&gt;': '>',
962             '&lt;': '<',
963             '&quot;': '"'
964         }, keys = [], p, regex;
965         
966         for (p in entities) {
967             keys.push(p);
968         }
969         
970         regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
971         
972         return function(value) {
973             return (!value) ? value : String(value).replace(regex, function(match, capture) {
974                 if (capture in entities) {
975                     return entities[capture];
976                 } else {
977                     return String.fromCharCode(parseInt(capture.substr(2), 10));
978                 }
979             });
980         };
981     })(),
982
983     /**
984      * Appends content to the query string of a URL, handling logic for whether to place
985      * a question mark or ampersand.
986      * @param {String} url The URL to append to.
987      * @param {String} string The content to append to the URL.
988      * @return (String) The resulting URL
989      */
990     urlAppend : function(url, string) {
991         if (!Ext.isEmpty(string)) {
992             return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
993         }
994
995         return url;
996     },
997
998     /**
999      * Trims whitespace from either end of a string, leaving spaces within the string intact.  Example:
1000      * @example
1001 var s = '  foo bar  ';
1002 alert('-' + s + '-');         //alerts "- foo bar -"
1003 alert('-' + Ext.String.trim(s) + '-');  //alerts "-foo bar-"
1004
1005      * @param {String} string The string to escape
1006      * @return {String} The trimmed string
1007      */
1008     trim: function(string) {
1009         return string.replace(Ext.String.trimRegex, "");
1010     },
1011
1012     /**
1013      * Capitalize the given string
1014      * @param {String} string
1015      * @return {String}
1016      */
1017     capitalize: function(string) {
1018         return string.charAt(0).toUpperCase() + string.substr(1);
1019     },
1020
1021     /**
1022      * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
1023      * @param {String} value The string to truncate
1024      * @param {Number} length The maximum length to allow before truncating
1025      * @param {Boolean} word True to try to find a common word break
1026      * @return {String} The converted text
1027      */
1028     ellipsis: function(value, len, word) {
1029         if (value && value.length > len) {
1030             if (word) {
1031                 var vs = value.substr(0, len - 2),
1032                 index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
1033                 if (index !== -1 && index >= (len - 15)) {
1034                     return vs.substr(0, index) + "...";
1035                 }
1036             }
1037             return value.substr(0, len - 3) + "...";
1038         }
1039         return value;
1040     },
1041
1042     /**
1043      * Escapes the passed string for use in a regular expression
1044      * @param {String} string
1045      * @return {String}
1046      */
1047     escapeRegex: function(string) {
1048         return string.replace(Ext.String.escapeRegexRe, "\\$1");
1049     },
1050
1051     /**
1052      * Escapes the passed string for ' and \
1053      * @param {String} string The string to escape
1054      * @return {String} The escaped string
1055      */
1056     escape: function(string) {
1057         return string.replace(Ext.String.escapeRe, "\\$1");
1058     },
1059
1060     /**
1061      * Utility function that allows you to easily switch a string between two alternating values.  The passed value
1062      * is compared to the current string, and if they are equal, the other value that was passed in is returned.  If
1063      * they are already different, the first value passed in is returned.  Note that this method returns the new value
1064      * but does not change the current string.
1065      * <pre><code>
1066     // alternate sort directions
1067     sort = Ext.String.toggle(sort, 'ASC', 'DESC');
1068
1069     // instead of conditional logic:
1070     sort = (sort == 'ASC' ? 'DESC' : 'ASC');
1071        </code></pre>
1072      * @param {String} string The current string
1073      * @param {String} value The value to compare to the current string
1074      * @param {String} other The new value to use if the string already equals the first value passed in
1075      * @return {String} The new value
1076      */
1077     toggle: function(string, value, other) {
1078         return string === value ? other : value;
1079     },
1080
1081     /**
1082      * Pads the left side of a string with a specified character.  This is especially useful
1083      * for normalizing number and date strings.  Example usage:
1084      *
1085      * <pre><code>
1086 var s = Ext.String.leftPad('123', 5, '0');
1087 // s now contains the string: '00123'
1088        </code></pre>
1089      * @param {String} string The original string
1090      * @param {Number} size The total length of the output string
1091      * @param {String} character (optional) The character with which to pad the original string (defaults to empty string " ")
1092      * @return {String} The padded string
1093      */
1094     leftPad: function(string, size, character) {
1095         var result = String(string);
1096         character = character || " ";
1097         while (result.length < size) {
1098             result = character + result;
1099         }
1100         return result;
1101     },
1102
1103     /**
1104      * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens.  Each
1105      * token must be unique, and must increment in the format {0}, {1}, etc.  Example usage:
1106      * <pre><code>
1107 var cls = 'my-class', text = 'Some text';
1108 var s = Ext.String.format('&lt;div class="{0}">{1}&lt;/div>', cls, text);
1109 // s now contains the string: '&lt;div class="my-class">Some text&lt;/div>'
1110        </code></pre>
1111      * @param {String} string The tokenized string to be formatted
1112      * @param {String} value1 The value to replace token {0}
1113      * @param {String} value2 Etc...
1114      * @return {String} The formatted string
1115      */
1116     format: function(format) {
1117         var args = Ext.Array.toArray(arguments, 1);
1118         return format.replace(Ext.String.formatRe, function(m, i) {
1119             return args[i];
1120         });
1121     }
1122 };
1123
1124 /**
1125  * @class Ext.Number
1126  *
1127  * A collection of useful static methods to deal with numbers
1128  * @singleton
1129  */
1130
1131 (function() {
1132
1133 var isToFixedBroken = (0.9).toFixed() !== '1';
1134
1135 Ext.Number = {
1136     /**
1137      * Checks whether or not the current number is within a desired range.  If the number is already within the
1138      * range it is returned, otherwise the min or max value is returned depending on which side of the range is
1139      * exceeded. Note that this method returns the constrained value but does not change the current number.
1140      * @param {Number} number The number to check
1141      * @param {Number} min The minimum number in the range
1142      * @param {Number} max The maximum number in the range
1143      * @return {Number} The constrained value if outside the range, otherwise the current value
1144      */
1145     constrain: function(number, min, max) {
1146         number = parseFloat(number);
1147
1148         if (!isNaN(min)) {
1149             number = Math.max(number, min);
1150         }
1151         if (!isNaN(max)) {
1152             number = Math.min(number, max);
1153         }
1154         return number;
1155     },
1156
1157     /**
1158      * Formats a number using fixed-point notation
1159      * @param {Number} value The number to format
1160      * @param {Number} precision The number of digits to show after the decimal point
1161      */
1162     toFixed: function(value, precision) {
1163         if (isToFixedBroken) {
1164             precision = precision || 0;
1165             var pow = Math.pow(10, precision);
1166             return (Math.round(value * pow) / pow).toFixed(precision);
1167         }
1168
1169         return value.toFixed(precision);
1170     },
1171
1172     /**
1173      * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
1174      * it is not.
1175
1176 Ext.Number.from('1.23', 1); // returns 1.23
1177 Ext.Number.from('abc', 1); // returns 1
1178
1179      * @param {Mixed} value
1180      * @param {Number} defaultValue The value to return if the original value is non-numeric
1181      * @return {Number} value, if numeric, defaultValue otherwise
1182      */
1183     from: function(value, defaultValue) {
1184         if (isFinite(value)) {
1185             value = parseFloat(value);
1186         }
1187
1188         return !isNaN(value) ? value : defaultValue;
1189     }
1190 };
1191
1192 })();
1193
1194 /**
1195  * This method is deprecated, please use {@link Ext.Number#from Ext.Number.from} instead
1196  *
1197  * @deprecated 4.0.0 Replaced by Ext.Number.from
1198  * @member Ext
1199  * @method num
1200  */
1201 Ext.num = function() {
1202     return Ext.Number.from.apply(this, arguments);
1203 };
1204 /**
1205  * @author Jacky Nguyen <jacky@sencha.com>
1206  * @docauthor Jacky Nguyen <jacky@sencha.com>
1207  * @class Ext.Array
1208  *
1209  * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
1210
1211  * @singleton
1212  * @markdown
1213  */
1214 (function() {
1215
1216     var arrayPrototype = Array.prototype,
1217         slice = arrayPrototype.slice,
1218         supportsForEach = 'forEach' in arrayPrototype,
1219         supportsMap = 'map' in arrayPrototype,
1220         supportsIndexOf = 'indexOf' in arrayPrototype,
1221         supportsEvery = 'every' in arrayPrototype,
1222         supportsSome = 'some' in arrayPrototype,
1223         supportsFilter = 'filter' in arrayPrototype,
1224         supportsSort = function() {
1225             var a = [1,2,3,4,5].sort(function(){ return 0; });
1226             return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
1227         }(),
1228         supportsSliceOnNodeList = true,
1229         ExtArray;
1230     try {
1231         // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
1232         if (typeof document !== 'undefined') {
1233             slice.call(document.getElementsByTagName('body'));
1234         }
1235     } catch (e) {
1236         supportsSliceOnNodeList = false;
1237     }
1238
1239     ExtArray = Ext.Array = {
1240         /**
1241          * Iterates an array or an iterable value and invoke the given callback function for each item.
1242
1243     var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
1244
1245     Ext.Array.each(countries, function(name, index, countriesItSelf) {
1246         console.log(name);
1247     });
1248
1249     var sum = function() {
1250         var sum = 0;
1251
1252         Ext.Array.each(arguments, function(value) {
1253             sum += value;
1254         });
1255
1256         return sum;
1257     };
1258
1259     sum(1, 2, 3); // returns 6
1260
1261          * The iteration can be stopped by returning false in the function callback.
1262
1263     Ext.Array.each(countries, function(name, index, countriesItSelf) {
1264         if (name === 'Singapore') {
1265             return false; // break here
1266         }
1267     });
1268
1269          * @param {Array/NodeList/Mixed} iterable The value to be iterated. If this
1270          * argument is not iterable, the callback function is called once.
1271          * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
1272          * the current `index`. Arguments passed to this callback function are:
1273
1274 - `item`: {Mixed} The item at the current `index` in the passed `array`
1275 - `index`: {Number} The current `index` within the `array`
1276 - `allItems`: {Array/NodeList/Mixed} The `array` passed as the first argument to `Ext.Array.each`
1277
1278          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
1279          * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
1280          * Defaults false
1281          * @return {Boolean} See description for the `fn` parameter.
1282          * @markdown
1283          */
1284         each: function(array, fn, scope, reverse) {
1285             array = ExtArray.from(array);
1286
1287             var i,
1288                 ln = array.length;
1289
1290             if (reverse !== true) {
1291                 for (i = 0; i < ln; i++) {
1292                     if (fn.call(scope || array[i], array[i], i, array) === false) {
1293                         return i;
1294                     }
1295                 }
1296             }
1297             else {
1298                 for (i = ln - 1; i > -1; i--) {
1299                     if (fn.call(scope || array[i], array[i], i, array) === false) {
1300                         return i;
1301                     }
1302                 }
1303             }
1304
1305             return true;
1306         },
1307
1308         /**
1309          * Iterates an array and invoke the given callback function for each item. Note that this will simply
1310          * delegate to the native Array.prototype.forEach method if supported.
1311          * It doesn't support stopping the iteration by returning false in the callback function like
1312          * {@link Ext.Array#each}. However, performance could be much better in modern browsers comparing with
1313          * {@link Ext.Array#each}
1314          *
1315          * @param {Array} array The array to iterate
1316          * @param {Function} fn The function callback, to be invoked these arguments:
1317          *
1318 - `item`: {Mixed} The item at the current `index` in the passed `array`
1319 - `index`: {Number} The current `index` within the `array`
1320 - `allItems`: {Array} The `array` itself which was passed as the first argument
1321
1322          * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
1323          * @markdown
1324          */
1325         forEach: function(array, fn, scope) {
1326             if (supportsForEach) {
1327                 return array.forEach(fn, scope);
1328             }
1329
1330             var i = 0,
1331                 ln = array.length;
1332
1333             for (; i < ln; i++) {
1334                 fn.call(scope, array[i], i, array);
1335             }
1336         },
1337
1338         /**
1339          * Get the index of the provided `item` in the given `array`, a supplement for the
1340          * missing arrayPrototype.indexOf in Internet Explorer.
1341          *
1342          * @param {Array} array The array to check
1343          * @param {Mixed} item The item to look for
1344          * @param {Number} from (Optional) The index at which to begin the search
1345          * @return {Number} The index of item in the array (or -1 if it is not found)
1346          * @markdown
1347          */
1348         indexOf: function(array, item, from) {
1349             if (supportsIndexOf) {
1350                 return array.indexOf(item, from);
1351             }
1352
1353             var i, length = array.length;
1354
1355             for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
1356                 if (array[i] === item) {
1357                     return i;
1358                 }
1359             }
1360
1361             return -1;
1362         },
1363
1364         /**
1365          * Checks whether or not the given `array` contains the specified `item`
1366          *
1367          * @param {Array} array The array to check
1368          * @param {Mixed} item The item to look for
1369          * @return {Boolean} True if the array contains the item, false otherwise
1370          * @markdown
1371          */
1372         contains: function(array, item) {
1373             if (supportsIndexOf) {
1374                 return array.indexOf(item) !== -1;
1375             }
1376
1377             var i, ln;
1378
1379             for (i = 0, ln = array.length; i < ln; i++) {
1380                 if (array[i] === item) {
1381                     return true;
1382                 }
1383             }
1384
1385             return false;
1386         },
1387
1388         /**
1389          * Converts any iterable (numeric indices and a length property) into a true array.
1390
1391 function test() {
1392     var args = Ext.Array.toArray(arguments),
1393         fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
1394
1395     alert(args.join(' '));
1396     alert(fromSecondToLastArgs.join(' '));
1397 }
1398
1399 test('just', 'testing', 'here'); // alerts 'just testing here';
1400                                  // alerts 'testing here';
1401
1402 Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
1403 Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
1404 Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
1405
1406          * @param {Mixed} iterable the iterable object to be turned into a true Array.
1407          * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
1408          * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
1409          * index of the iterable value
1410          * @return {Array} array
1411          * @markdown
1412          */
1413         toArray: function(iterable, start, end){
1414             if (!iterable || !iterable.length) {
1415                 return [];
1416             }
1417
1418             if (typeof iterable === 'string') {
1419                 iterable = iterable.split('');
1420             }
1421
1422             if (supportsSliceOnNodeList) {
1423                 return slice.call(iterable, start || 0, end || iterable.length);
1424             }
1425
1426             var array = [],
1427                 i;
1428
1429             start = start || 0;
1430             end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
1431
1432             for (i = start; i < end; i++) {
1433                 array.push(iterable[i]);
1434             }
1435
1436             return array;
1437         },
1438
1439         /**
1440          * Plucks the value of a property from each item in the Array. Example:
1441          *
1442     Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
1443
1444          * @param {Array|NodeList} array The Array of items to pluck the value from.
1445          * @param {String} propertyName The property name to pluck from each element.
1446          * @return {Array} The value from each item in the Array.
1447          */
1448         pluck: function(array, propertyName) {
1449             var ret = [],
1450                 i, ln, item;
1451
1452             for (i = 0, ln = array.length; i < ln; i++) {
1453                 item = array[i];
1454
1455                 ret.push(item[propertyName]);
1456             }
1457
1458             return ret;
1459         },
1460
1461         /**
1462          * Creates a new array with the results of calling a provided function on every element in this array.
1463          * @param {Array} array
1464          * @param {Function} fn Callback function for each item
1465          * @param {Object} scope Callback function scope
1466          * @return {Array} results
1467          */
1468         map: function(array, fn, scope) {
1469             if (supportsMap) {
1470                 return array.map(fn, scope);
1471             }
1472
1473             var results = [],
1474                 i = 0,
1475                 len = array.length;
1476
1477             for (; i < len; i++) {
1478                 results[i] = fn.call(scope, array[i], i, array);
1479             }
1480
1481             return results;
1482         },
1483
1484         /**
1485          * Executes the specified function for each array element until the function returns a falsy value.
1486          * If such an item is found, the function will return false immediately.
1487          * Otherwise, it will return true.
1488          *
1489          * @param {Array} array
1490          * @param {Function} fn Callback function for each item
1491          * @param {Object} scope Callback function scope
1492          * @return {Boolean} True if no false value is returned by the callback function.
1493          */
1494         every: function(array, fn, scope) {
1495             if (!fn) {
1496                 Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
1497             }
1498             if (supportsEvery) {
1499                 return array.every(fn, scope);
1500             }
1501
1502             var i = 0,
1503                 ln = array.length;
1504
1505             for (; i < ln; ++i) {
1506                 if (!fn.call(scope, array[i], i, array)) {
1507                     return false;
1508                 }
1509             }
1510
1511             return true;
1512         },
1513
1514         /**
1515          * Executes the specified function for each array element until the function returns a truthy value.
1516          * If such an item is found, the function will return true immediately. Otherwise, it will return false.
1517          *
1518          * @param {Array} array
1519          * @param {Function} fn Callback function for each item
1520          * @param {Object} scope Callback function scope
1521          * @return {Boolean} True if the callback function returns a truthy value.
1522          */
1523         some: function(array, fn, scope) {
1524             if (!fn) {
1525                 Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
1526             }
1527             if (supportsSome) {
1528                 return array.some(fn, scope);
1529             }
1530
1531             var i = 0,
1532                 ln = array.length;
1533
1534             for (; i < ln; ++i) {
1535                 if (fn.call(scope, array[i], i, array)) {
1536                     return true;
1537                 }
1538             }
1539
1540             return false;
1541         },
1542
1543         /**
1544          * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
1545          *
1546          * @see Ext.Array.filter
1547          * @param {Array} array
1548          * @return {Array} results
1549          */
1550         clean: function(array) {
1551             var results = [],
1552                 i = 0,
1553                 ln = array.length,
1554                 item;
1555
1556             for (; i < ln; i++) {
1557                 item = array[i];
1558
1559                 if (!Ext.isEmpty(item)) {
1560                     results.push(item);
1561                 }
1562             }
1563
1564             return results;
1565         },
1566
1567         /**
1568          * Returns a new array with unique items
1569          *
1570          * @param {Array} array
1571          * @return {Array} results
1572          */
1573         unique: function(array) {
1574             var clone = [],
1575                 i = 0,
1576                 ln = array.length,
1577                 item;
1578
1579             for (; i < ln; i++) {
1580                 item = array[i];
1581
1582                 if (ExtArray.indexOf(clone, item) === -1) {
1583                     clone.push(item);
1584                 }
1585             }
1586
1587             return clone;
1588         },
1589
1590         /**
1591          * Creates a new array with all of the elements of this array for which
1592          * the provided filtering function returns true.
1593          * @param {Array} array
1594          * @param {Function} fn Callback function for each item
1595          * @param {Object} scope Callback function scope
1596          * @return {Array} results
1597          */
1598         filter: function(array, fn, scope) {
1599             if (supportsFilter) {
1600                 return array.filter(fn, scope);
1601             }
1602
1603             var results = [],
1604                 i = 0,
1605                 ln = array.length;
1606
1607             for (; i < ln; i++) {
1608                 if (fn.call(scope, array[i], i, array)) {
1609                     results.push(array[i]);
1610                 }
1611             }
1612
1613             return results;
1614         },
1615
1616         /**
1617          * Converts a value to an array if it's not already an array; returns:
1618          *
1619          * - An empty array if given value is `undefined` or `null`
1620          * - Itself if given value is already an array
1621          * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
1622          * - An array with one item which is the given value, otherwise
1623          *
1624          * @param {Array/Mixed} value The value to convert to an array if it's not already is an array
1625          * @param {Boolean} (Optional) newReference True to clone the given array and return a new reference if necessary,
1626          * defaults to false
1627          * @return {Array} array
1628          * @markdown
1629          */
1630         from: function(value, newReference) {
1631             if (value === undefined || value === null) {
1632                 return [];
1633             }
1634
1635             if (Ext.isArray(value)) {
1636                 return (newReference) ? slice.call(value) : value;
1637             }
1638
1639             if (value && value.length !== undefined && typeof value !== 'string') {
1640                 return Ext.toArray(value);
1641             }
1642
1643             return [value];
1644         },
1645
1646         /**
1647          * Removes the specified item from the array if it exists
1648          *
1649          * @param {Array} array The array
1650          * @param {Mixed} item The item to remove
1651          * @return {Array} The passed array itself
1652          */
1653         remove: function(array, item) {
1654             var index = ExtArray.indexOf(array, item);
1655
1656             if (index !== -1) {
1657                 array.splice(index, 1);
1658             }
1659
1660             return array;
1661         },
1662
1663         /**
1664          * Push an item into the array only if the array doesn't contain it yet
1665          *
1666          * @param {Array} array The array
1667          * @param {Mixed} item The item to include
1668          * @return {Array} The passed array itself
1669          */
1670         include: function(array, item) {
1671             if (!ExtArray.contains(array, item)) {
1672                 array.push(item);
1673             }
1674         },
1675
1676         /**
1677          * Clone a flat array without referencing the previous one. Note that this is different
1678          * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
1679          * for Array.prototype.slice.call(array)
1680          *
1681          * @param {Array} array The array
1682          * @return {Array} The clone array
1683          */
1684         clone: function(array) {
1685             return slice.call(array);
1686         },
1687
1688         /**
1689          * Merge multiple arrays into one with unique items. Alias to {@link Ext.Array#union}.
1690          *
1691          * @param {Array} array,...
1692          * @return {Array} merged
1693          */
1694         merge: function() {
1695             var args = slice.call(arguments),
1696                 array = [],
1697                 i, ln;
1698
1699             for (i = 0, ln = args.length; i < ln; i++) {
1700                 array = array.concat(args[i]);
1701             }
1702
1703             return ExtArray.unique(array);
1704         },
1705
1706         /**
1707          * Merge multiple arrays into one with unique items that exist in all of the arrays.
1708          *
1709          * @param {Array} array,...
1710          * @return {Array} intersect
1711          */
1712         intersect: function() {
1713             var intersect = [],
1714                 arrays = slice.call(arguments),
1715                 i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
1716
1717             if (!arrays.length) {
1718                 return intersect;
1719             }
1720
1721             // Find the smallest array
1722             for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
1723                 if (!minArray || array.length < minArray.length) {
1724                     minArray = array;
1725                     x = i;
1726                 }
1727             }
1728
1729             minArray = Ext.Array.unique(minArray);
1730             arrays.splice(x, 1);
1731
1732             // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
1733             // an item in the small array, we're likely to find it before reaching the end
1734             // of the inner loop and can terminate the search early.
1735             for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
1736                 var count = 0;
1737
1738                 for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
1739                     for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
1740                         if (x === y) {
1741                             count++;
1742                             break;
1743                         }
1744                     }
1745                 }
1746
1747                 if (count === arraysLn) {
1748                     intersect.push(x);
1749                 }
1750             }
1751
1752             return intersect;
1753         },
1754
1755         /**
1756          * Perform a set difference A-B by subtracting all items in array B from array A.
1757          *
1758          * @param {Array} array A
1759          * @param {Array} array B
1760          * @return {Array} difference
1761          */
1762         difference: function(arrayA, arrayB) {
1763             var clone = slice.call(arrayA),
1764                 ln = clone.length,
1765                 i, j, lnB;
1766
1767             for (i = 0,lnB = arrayB.length; i < lnB; i++) {
1768                 for (j = 0; j < ln; j++) {
1769                     if (clone[j] === arrayB[i]) {
1770                         clone.splice(j, 1);
1771                         j--;
1772                         ln--;
1773                     }
1774                 }
1775             }
1776
1777             return clone;
1778         },
1779
1780         /**
1781          * Sorts the elements of an Array.
1782          * By default, this method sorts the elements alphabetically and ascending.
1783          *
1784          * @param {Array} array The array to sort.
1785          * @param {Function} sortFn (optional) The comparison function.
1786          * @return {Array} The sorted array.
1787          */
1788         sort: function(array, sortFn) {
1789             if (supportsSort) {
1790                 if (sortFn) {
1791                     return array.sort(sortFn);
1792                 } else {
1793                     return array.sort();
1794                 }
1795             }
1796
1797             var length = array.length,
1798                 i = 0,
1799                 comparison,
1800                 j, min, tmp;
1801
1802             for (; i < length; i++) {
1803                 min = i;
1804                 for (j = i + 1; j < length; j++) {
1805                     if (sortFn) {
1806                         comparison = sortFn(array[j], array[min]);
1807                         if (comparison < 0) {
1808                             min = j;
1809                         }
1810                     } else if (array[j] < array[min]) {
1811                         min = j;
1812                     }
1813                 }
1814                 if (min !== i) {
1815                     tmp = array[i];
1816                     array[i] = array[min];
1817                     array[min] = tmp;
1818                 }
1819             }
1820
1821             return array;
1822         },
1823
1824         /**
1825          * Recursively flattens into 1-d Array. Injects Arrays inline.
1826          * @param {Array} array The array to flatten
1827          * @return {Array} The new, flattened array.
1828          */
1829         flatten: function(array) {
1830             var worker = [];
1831
1832             function rFlatten(a) {
1833                 var i, ln, v;
1834
1835                 for (i = 0, ln = a.length; i < ln; i++) {
1836                     v = a[i];
1837
1838                     if (Ext.isArray(v)) {
1839                         rFlatten(v);
1840                     } else {
1841                         worker.push(v);
1842                     }
1843                 }
1844
1845                 return worker;
1846             }
1847
1848             return rFlatten(array);
1849         },
1850
1851         /**
1852          * Returns the minimum value in the Array.
1853          * @param {Array|NodeList} array The Array from which to select the minimum value.
1854          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
1855          *                   If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
1856          * @return {Mixed} minValue The minimum value
1857          */
1858         min: function(array, comparisonFn) {
1859             var min = array[0],
1860                 i, ln, item;
1861
1862             for (i = 0, ln = array.length; i < ln; i++) {
1863                 item = array[i];
1864
1865                 if (comparisonFn) {
1866                     if (comparisonFn(min, item) === 1) {
1867                         min = item;
1868                     }
1869                 }
1870                 else {
1871                     if (item < min) {
1872                         min = item;
1873                     }
1874                 }
1875             }
1876
1877             return min;
1878         },
1879
1880         /**
1881          * Returns the maximum value in the Array
1882          * @param {Array|NodeList} array The Array from which to select the maximum value.
1883          * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
1884          *                   If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
1885          * @return {Mixed} maxValue The maximum value
1886          */
1887         max: function(array, comparisonFn) {
1888             var max = array[0],
1889                 i, ln, item;
1890
1891             for (i = 0, ln = array.length; i < ln; i++) {
1892                 item = array[i];
1893
1894                 if (comparisonFn) {
1895                     if (comparisonFn(max, item) === -1) {
1896                         max = item;
1897                     }
1898                 }
1899                 else {
1900                     if (item > max) {
1901                         max = item;
1902                     }
1903                 }
1904             }
1905
1906             return max;
1907         },
1908
1909         /**
1910          * Calculates the mean of all items in the array
1911          * @param {Array} array The Array to calculate the mean value of.
1912          * @return {Number} The mean.
1913          */
1914         mean: function(array) {
1915             return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
1916         },
1917
1918         /**
1919          * Calculates the sum of all items in the given array
1920          * @param {Array} array The Array to calculate the sum value of.
1921          * @return {Number} The sum.
1922          */
1923         sum: function(array) {
1924             var sum = 0,
1925                 i, ln, item;
1926
1927             for (i = 0,ln = array.length; i < ln; i++) {
1928                 item = array[i];
1929
1930                 sum += item;
1931             }
1932
1933             return sum;
1934         }
1935
1936     };
1937
1938     /**
1939      * Convenient alias to {@link Ext.Array#each}
1940      * @member Ext
1941      * @method each
1942      */
1943     Ext.each = Ext.Array.each;
1944
1945     /**
1946      * Alias to {@link Ext.Array#merge}.
1947      * @member Ext.Array
1948      * @method union
1949      */
1950     Ext.Array.union = Ext.Array.merge;
1951
1952     /**
1953      * Old alias to {@link Ext.Array#min}
1954      * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
1955      * @member Ext
1956      * @method min
1957      */
1958     Ext.min = Ext.Array.min;
1959
1960     /**
1961      * Old alias to {@link Ext.Array#max}
1962      * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
1963      * @member Ext
1964      * @method max
1965      */
1966     Ext.max = Ext.Array.max;
1967
1968     /**
1969      * Old alias to {@link Ext.Array#sum}
1970      * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
1971      * @member Ext
1972      * @method sum
1973      */
1974     Ext.sum = Ext.Array.sum;
1975
1976     /**
1977      * Old alias to {@link Ext.Array#mean}
1978      * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
1979      * @member Ext
1980      * @method mean
1981      */
1982     Ext.mean = Ext.Array.mean;
1983
1984     /**
1985      * Old alias to {@link Ext.Array#flatten}
1986      * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
1987      * @member Ext
1988      * @method flatten
1989      */
1990     Ext.flatten = Ext.Array.flatten;
1991
1992     /**
1993      * Old alias to {@link Ext.Array#clean Ext.Array.clean}
1994      * @deprecated 4.0.0 Use {@link Ext.Array.clean} instead
1995      * @member Ext
1996      * @method clean
1997      */
1998     Ext.clean = Ext.Array.clean;
1999
2000     /**
2001      * Old alias to {@link Ext.Array#unique Ext.Array.unique}
2002      * @deprecated 4.0.0 Use {@link Ext.Array.unique} instead
2003      * @member Ext
2004      * @method unique
2005      */
2006     Ext.unique = Ext.Array.unique;
2007
2008     /**
2009      * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
2010      * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
2011      * @member Ext
2012      * @method pluck
2013      */
2014     Ext.pluck = Ext.Array.pluck;
2015
2016     /**
2017      * Convenient alias to {@link Ext.Array#toArray Ext.Array.toArray}
2018      * @param {Iterable} the iterable object to be turned into a true Array.
2019      * @member Ext
2020      * @method toArray
2021      * @return {Array} array
2022      */
2023     Ext.toArray = function() {
2024         return ExtArray.toArray.apply(ExtArray, arguments);
2025     }
2026 })();
2027
2028 /**
2029  * @class Ext.Function
2030  *
2031  * A collection of useful static methods to deal with function callbacks
2032  * @singleton
2033  */
2034
2035 Ext.Function = {
2036
2037     /**
2038      * A very commonly used method throughout the framework. It acts as a wrapper around another method
2039      * which originally accepts 2 arguments for <code>name</code> and <code>value</code>.
2040      * The wrapped function then allows "flexible" value setting of either:
2041      *
2042      * <ul>
2043      *      <li><code>name</code> and <code>value</code> as 2 arguments</li>
2044      *      <li>one single object argument with multiple key - value pairs</li>
2045      * </ul>
2046      *
2047      * For example:
2048      * <pre><code>
2049 var setValue = Ext.Function.flexSetter(function(name, value) {
2050     this[name] = value;
2051 });
2052
2053 // Afterwards
2054 // Setting a single name - value
2055 setValue('name1', 'value1');
2056
2057 // Settings multiple name - value pairs
2058 setValue({
2059     name1: 'value1',
2060     name2: 'value2',
2061     name3: 'value3'
2062 });
2063      * </code></pre>
2064      * @param {Function} setter
2065      * @returns {Function} flexSetter
2066      */
2067     flexSetter: function(fn) {
2068         return function(a, b) {
2069             var k, i;
2070
2071             if (a === null) {
2072                 return this;
2073             }
2074
2075             if (typeof a !== 'string') {
2076                 for (k in a) {
2077                     if (a.hasOwnProperty(k)) {
2078                         fn.call(this, k, a[k]);
2079                     }
2080                 }
2081
2082                 if (Ext.enumerables) {
2083                     for (i = Ext.enumerables.length; i--;) {
2084                         k = Ext.enumerables[i];
2085                         if (a.hasOwnProperty(k)) {
2086                             fn.call(this, k, a[k]);
2087                         }
2088                     }
2089                 }
2090             } else {
2091                 fn.call(this, a, b);
2092             }
2093
2094             return this;
2095         };
2096     },
2097
2098    /**
2099      * Create a new function from the provided <code>fn</code>, change <code>this</code> to the provided scope, optionally
2100      * overrides arguments for the call. (Defaults to the arguments passed by the caller)
2101      *
2102      * @param {Function} fn The function to delegate.
2103      * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
2104      * <b>If omitted, defaults to the browser window.</b>
2105      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2106      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2107      * if a number the args are inserted at the specified position
2108      * @return {Function} The new function
2109      */
2110     bind: function(fn, scope, args, appendArgs) {
2111         var method = fn,
2112             applyArgs;
2113
2114         return function() {
2115             var callArgs = args || arguments;
2116
2117             if (appendArgs === true) {
2118                 callArgs = Array.prototype.slice.call(arguments, 0);
2119                 callArgs = callArgs.concat(args);
2120             }
2121             else if (Ext.isNumber(appendArgs)) {
2122                 callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
2123                 applyArgs = [appendArgs, 0].concat(args); // create method call params
2124                 Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
2125             }
2126
2127             return method.apply(scope || window, callArgs);
2128         };
2129     },
2130
2131     /**
2132      * Create a new function from the provided <code>fn</code>, the arguments of which are pre-set to `args`.
2133      * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
2134      * This is especially useful when creating callbacks.
2135      * For example:
2136      *
2137     var originalFunction = function(){
2138         alert(Ext.Array.from(arguments).join(' '));
2139     };
2140
2141     var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
2142
2143     callback(); // alerts 'Hello World'
2144     callback('by Me'); // alerts 'Hello World by Me'
2145
2146      * @param {Function} fn The original function
2147      * @param {Array} args The arguments to pass to new callback
2148      * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
2149      * @return {Function} The new callback function
2150      */
2151     pass: function(fn, args, scope) {
2152         if (args) {
2153             args = Ext.Array.from(args);
2154         }
2155
2156         return function() {
2157             return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
2158         };
2159     },
2160
2161     /**
2162      * Create an alias to the provided method property with name <code>methodName</code> of <code>object</code>.
2163      * Note that the execution scope will still be bound to the provided <code>object</code> itself.
2164      *
2165      * @param {Object/Function} object
2166      * @param {String} methodName
2167      * @return {Function} aliasFn
2168      */
2169     alias: function(object, methodName) {
2170         return function() {
2171             return object[methodName].apply(object, arguments);
2172         };
2173     },
2174
2175     /**
2176      * Creates an interceptor function. The passed function is called before the original one. If it returns false,
2177      * the original one is not called. The resulting function returns the results of the original function.
2178      * The passed function is called with the parameters of the original function. Example usage:
2179      * <pre><code>
2180 var sayHi = function(name){
2181     alert('Hi, ' + name);
2182 }
2183
2184 sayHi('Fred'); // alerts "Hi, Fred"
2185
2186 // create a new function that validates input without
2187 // directly modifying the original function:
2188 var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
2189     return name == 'Brian';
2190 });
2191
2192 sayHiToFriend('Fred');  // no alert
2193 sayHiToFriend('Brian'); // alerts "Hi, Brian"
2194      </code></pre>
2195      * @param {Function} origFn The original function.
2196      * @param {Function} newFn The function to call before the original
2197      * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the passed function is executed.
2198      * <b>If omitted, defaults to the scope in which the original function is called or the browser window.</b>
2199      * @param {Mixed} returnValue (optional) The value to return if the passed function return false (defaults to null).
2200      * @return {Function} The new function
2201      */
2202     createInterceptor: function(origFn, newFn, scope, returnValue) {
2203         var method = origFn;
2204         if (!Ext.isFunction(newFn)) {
2205             return origFn;
2206         }
2207         else {
2208             return function() {
2209                 var me = this,
2210                     args = arguments;
2211                 newFn.target = me;
2212                 newFn.method = origFn;
2213                 return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
2214             };
2215         }
2216     },
2217
2218     /**
2219     * Creates a delegate (callback) which, when called, executes after a specific delay.
2220     * @param {Function} fn The function which will be called on a delay when the returned function is called.
2221     * Optionally, a replacement (or additional) argument list may be specified.
2222     * @param {Number} delay The number of milliseconds to defer execution by whenever called.
2223     * @param {Object} scope (optional) The scope (<code>this</code> reference) used by the function at execution time.
2224     * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
2225     * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2226     * if a number the args are inserted at the specified position.
2227     * @return {Function} A function which, when called, executes the original function after the specified delay.
2228     */
2229     createDelayed: function(fn, delay, scope, args, appendArgs) {
2230         if (scope || args) {
2231             fn = Ext.Function.bind(fn, scope, args, appendArgs);
2232         }
2233         return function() {
2234             var me = this;
2235             setTimeout(function() {
2236                 fn.apply(me, arguments);
2237             }, delay);
2238         };
2239     },
2240
2241     /**
2242      * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
2243      * <pre><code>
2244 var sayHi = function(name){
2245     alert('Hi, ' + name);
2246 }
2247
2248 // executes immediately:
2249 sayHi('Fred');
2250
2251 // executes after 2 seconds:
2252 Ext.Function.defer(sayHi, 2000, this, ['Fred']);
2253
2254 // this syntax is sometimes useful for deferring
2255 // execution of an anonymous function:
2256 Ext.Function.defer(function(){
2257     alert('Anonymous');
2258 }, 100);
2259      </code></pre>
2260      * @param {Function} fn The function to defer.
2261      * @param {Number} millis The number of milliseconds for the setTimeout call (if less than or equal to 0 the function is executed immediately)
2262      * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
2263      * <b>If omitted, defaults to the browser window.</b>
2264      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2265      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2266      * if a number the args are inserted at the specified position
2267      * @return {Number} The timeout id that can be used with clearTimeout
2268      */
2269     defer: function(fn, millis, obj, args, appendArgs) {
2270         fn = Ext.Function.bind(fn, obj, args, appendArgs);
2271         if (millis > 0) {
2272             return setTimeout(fn, millis);
2273         }
2274         fn();
2275         return 0;
2276     },
2277
2278     /**
2279      * Create a combined function call sequence of the original function + the passed function.
2280      * The resulting function returns the results of the original function.
2281      * The passed function is called with the parameters of the original function. Example usage:
2282      *
2283      * <pre><code>
2284 var sayHi = function(name){
2285     alert('Hi, ' + name);
2286 }
2287
2288 sayHi('Fred'); // alerts "Hi, Fred"
2289
2290 var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
2291     alert('Bye, ' + name);
2292 });
2293
2294 sayGoodbye('Fred'); // both alerts show
2295      * </code></pre>
2296      *
2297      * @param {Function} origFn The original function.
2298      * @param {Function} newFn The function to sequence
2299      * @param {Object} scope (optional) The scope (this reference) in which the passed function is executed.
2300      * If omitted, defaults to the scope in which the original function is called or the browser window.
2301      * @return {Function} The new function
2302      */
2303     createSequence: function(origFn, newFn, scope) {
2304         if (!Ext.isFunction(newFn)) {
2305             return origFn;
2306         }
2307         else {
2308             return function() {
2309                 var retval = origFn.apply(this || window, arguments);
2310                 newFn.apply(scope || this || window, arguments);
2311                 return retval;
2312             };
2313         }
2314     },
2315
2316     /**
2317      * <p>Creates a delegate function, optionally with a bound scope which, when called, buffers
2318      * the execution of the passed function for the configured number of milliseconds.
2319      * If called again within that period, the impending invocation will be canceled, and the
2320      * timeout period will begin again.</p>
2321      *
2322      * @param {Function} fn The function to invoke on a buffered timer.
2323      * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
2324      * function.
2325      * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which
2326      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2327      * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
2328      * passed by the caller.
2329      * @return {Function} A function which invokes the passed function after buffering for the specified time.
2330      */
2331     createBuffered: function(fn, buffer, scope, args) {
2332         return function(){
2333             var timerId;
2334             return function() {
2335                 var me = this;
2336                 if (timerId) {
2337                     clearInterval(timerId);
2338                     timerId = null;
2339                 }
2340                 timerId = setTimeout(function(){
2341                     fn.apply(scope || me, args || arguments);
2342                 }, buffer);
2343             };
2344         }();
2345     },
2346
2347     /**
2348      * <p>Creates a throttled version of the passed function which, when called repeatedly and
2349      * rapidly, invokes the passed function only after a certain interval has elapsed since the
2350      * previous invocation.</p>
2351      *
2352      * <p>This is useful for wrapping functions which may be called repeatedly, such as
2353      * a handler of a mouse move event when the processing is expensive.</p>
2354      *
2355      * @param fn {Function} The function to execute at a regular time interval.
2356      * @param interval {Number} The interval <b>in milliseconds</b> on which the passed function is executed.
2357      * @param scope (optional) The scope (<code><b>this</b></code> reference) in which
2358      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2359      * @returns {Function} A function which invokes the passed function at the specified interval.
2360      */
2361     createThrottled: function(fn, interval, scope) {
2362         var lastCallTime, elapsed, lastArgs, timer, execute = function() {
2363             fn.apply(scope || this, lastArgs);
2364             lastCallTime = new Date().getTime();
2365         };
2366
2367         return function() {
2368             elapsed = new Date().getTime() - lastCallTime;
2369             lastArgs = arguments;
2370
2371             clearTimeout(timer);
2372             if (!lastCallTime || (elapsed >= interval)) {
2373                 execute();
2374             } else {
2375                 timer = setTimeout(execute, interval - elapsed);
2376             }
2377         };
2378     }
2379 };
2380
2381 /**
2382  * Shorthand for {@link Ext.Function#defer}
2383  * @member Ext
2384  * @method defer
2385  */
2386 Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
2387
2388 /**
2389  * Shorthand for {@link Ext.Function#pass}
2390  * @member Ext
2391  * @method pass
2392  */
2393 Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
2394
2395 /**
2396  * Shorthand for {@link Ext.Function#bind}
2397  * @member Ext
2398  * @method bind
2399  */
2400 Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
2401
2402 /**
2403  * @author Jacky Nguyen <jacky@sencha.com>
2404  * @docauthor Jacky Nguyen <jacky@sencha.com>
2405  * @class Ext.Object
2406  *
2407  * A collection of useful static methods to deal with objects
2408  *
2409  * @singleton
2410  */
2411
2412 (function() {
2413
2414 var ExtObject = Ext.Object = {
2415
2416     /**
2417      * Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct
2418      * query strings. For example:
2419
2420     var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
2421
2422     // objects then equals:
2423     [
2424         { name: 'hobbies', value: 'reading' },
2425         { name: 'hobbies', value: 'cooking' },
2426         { name: 'hobbies', value: 'swimming' },
2427     ];
2428
2429     var objects = Ext.Object.toQueryObjects('dateOfBirth', {
2430         day: 3,
2431         month: 8,
2432         year: 1987,
2433         extra: {
2434             hour: 4
2435             minute: 30
2436         }
2437     }, true); // Recursive
2438
2439     // objects then equals:
2440     [
2441         { name: 'dateOfBirth[day]', value: 3 },
2442         { name: 'dateOfBirth[month]', value: 8 },
2443         { name: 'dateOfBirth[year]', value: 1987 },
2444         { name: 'dateOfBirth[extra][hour]', value: 4 },
2445         { name: 'dateOfBirth[extra][minute]', value: 30 },
2446     ];
2447
2448      * @param {String} name
2449      * @param {Mixed} value
2450      * @param {Boolean} recursive
2451      * @markdown
2452      */
2453     toQueryObjects: function(name, value, recursive) {
2454         var self = ExtObject.toQueryObjects,
2455             objects = [],
2456             i, ln;
2457
2458         if (Ext.isArray(value)) {
2459             for (i = 0, ln = value.length; i < ln; i++) {
2460                 if (recursive) {
2461                     objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2462                 }
2463                 else {
2464                     objects.push({
2465                         name: name,
2466                         value: value[i]
2467                     });
2468                 }
2469             }
2470         }
2471         else if (Ext.isObject(value)) {
2472             for (i in value) {
2473                 if (value.hasOwnProperty(i)) {
2474                     if (recursive) {
2475                         objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2476                     }
2477                     else {
2478                         objects.push({
2479                             name: name,
2480                             value: value[i]
2481                         });
2482                     }
2483                 }
2484             }
2485         }
2486         else {
2487             objects.push({
2488                 name: name,
2489                 value: value
2490             });
2491         }
2492
2493         return objects;
2494     },
2495
2496     /**
2497      * Takes an object and converts it to an encoded query string
2498
2499 - Non-recursive:
2500
2501     Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
2502     Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
2503     Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
2504     Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
2505     Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
2506
2507 - Recursive:
2508
2509     Ext.Object.toQueryString({
2510         username: 'Jacky',
2511         dateOfBirth: {
2512             day: 1,
2513             month: 2,
2514             year: 1911
2515         },
2516         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2517     }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
2518               // username=Jacky
2519               //    &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
2520               //    &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
2521
2522      *
2523      * @param {Object} object The object to encode
2524      * @param {Boolean} recursive (optional) Whether or not to interpret the object in recursive format.
2525      * (PHP / Ruby on Rails servers and similar). Defaults to false
2526      * @return {String} queryString
2527      * @markdown
2528      */
2529     toQueryString: function(object, recursive) {
2530         var paramObjects = [],
2531             params = [],
2532             i, j, ln, paramObject, value;
2533
2534         for (i in object) {
2535             if (object.hasOwnProperty(i)) {
2536                 paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
2537             }
2538         }
2539
2540         for (j = 0, ln = paramObjects.length; j < ln; j++) {
2541             paramObject = paramObjects[j];
2542             value = paramObject.value;
2543
2544             if (Ext.isEmpty(value)) {
2545                 value = '';
2546             }
2547             else if (Ext.isDate(value)) {
2548                 value = Ext.Date.toString(value);
2549             }
2550
2551             params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
2552         }
2553
2554         return params.join('&');
2555     },
2556
2557     /**
2558      * Converts a query string back into an object.
2559      *
2560 - Non-recursive:
2561
2562     Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
2563     Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
2564     Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
2565     Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
2566
2567 - Recursive:
2568
2569     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);
2570
2571     // returns
2572     {
2573         username: 'Jacky',
2574         dateOfBirth: {
2575             day: '1',
2576             month: '2',
2577             year: '1911'
2578         },
2579         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2580     }
2581
2582      * @param {String} queryString The query string to decode
2583      * @param {Boolean} recursive (Optional) Whether or not to recursively decode the string. This format is supported by
2584      * PHP / Ruby on Rails servers and similar. Defaults to false
2585      * @return {Object}
2586      */
2587     fromQueryString: function(queryString, recursive) {
2588         var parts = queryString.replace(/^\?/, '').split('&'),
2589             object = {},
2590             temp, components, name, value, i, ln,
2591             part, j, subLn, matchedKeys, matchedName,
2592             keys, key, nextKey;
2593
2594         for (i = 0, ln = parts.length; i < ln; i++) {
2595             part = parts[i];
2596
2597             if (part.length > 0) {
2598                 components = part.split('=');
2599                 name = decodeURIComponent(components[0]);
2600                 value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
2601
2602                 if (!recursive) {
2603                     if (object.hasOwnProperty(name)) {
2604                         if (!Ext.isArray(object[name])) {
2605                             object[name] = [object[name]];
2606                         }
2607
2608                         object[name].push(value);
2609                     }
2610                     else {
2611                         object[name] = value;
2612                     }
2613                 }
2614                 else {
2615                     matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
2616                     matchedName = name.match(/^([^\[]+)/);
2617
2618                     if (!matchedName) {
2619                         Ext.Error.raise({
2620                             sourceClass: "Ext.Object",
2621                             sourceMethod: "fromQueryString",
2622                             queryString: queryString,
2623                             recursive: recursive,
2624                             msg: 'Malformed query string given, failed parsing name from "' + part + '"'
2625                         });
2626                     }
2627
2628                     name = matchedName[0];
2629                     keys = [];
2630
2631                     if (matchedKeys === null) {
2632                         object[name] = value;
2633                         continue;
2634                     }
2635
2636                     for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
2637                         key = matchedKeys[j];
2638                         key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
2639                         keys.push(key);
2640                     }
2641
2642                     keys.unshift(name);
2643
2644                     temp = object;
2645
2646                     for (j = 0, subLn = keys.length; j < subLn; j++) {
2647                         key = keys[j];
2648
2649                         if (j === subLn - 1) {
2650                             if (Ext.isArray(temp) && key === '') {
2651                                 temp.push(value);
2652                             }
2653                             else {
2654                                 temp[key] = value;
2655                             }
2656                         }
2657                         else {
2658                             if (temp[key] === undefined || typeof temp[key] === 'string') {
2659                                 nextKey = keys[j+1];
2660
2661                                 temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
2662                             }
2663
2664                             temp = temp[key];
2665                         }
2666                     }
2667                 }
2668             }
2669         }
2670
2671         return object;
2672     },
2673
2674     /**
2675      * Iterate through an object and invoke the given callback function for each iteration. The iteration can be stop
2676      * by returning `false` in the callback function. For example:
2677
2678     var person = {
2679         name: 'Jacky'
2680         hairColor: 'black'
2681         loves: ['food', 'sleeping', 'wife']
2682     };
2683
2684     Ext.Object.each(person, function(key, value, myself) {
2685         console.log(key + ":" + value);
2686
2687         if (key === 'hairColor') {
2688             return false; // stop the iteration
2689         }
2690     });
2691
2692      * @param {Object} object The object to iterate
2693      * @param {Function} fn The callback function. Passed arguments for each iteration are:
2694
2695 - {String} `key`
2696 - {Mixed} `value`
2697 - {Object} `object` The object itself
2698
2699      * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
2700      * @markdown
2701      */
2702     each: function(object, fn, scope) {
2703         for (var property in object) {
2704             if (object.hasOwnProperty(property)) {
2705                 if (fn.call(scope || object, property, object[property], object) === false) {
2706                     return;
2707                 }
2708             }
2709         }
2710     },
2711
2712     /**
2713      * Merges any number of objects recursively without referencing them or their children.
2714
2715     var extjs = {
2716         companyName: 'Ext JS',
2717         products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
2718         isSuperCool: true
2719         office: {
2720             size: 2000,
2721             location: 'Palo Alto',
2722             isFun: true
2723         }
2724     };
2725
2726     var newStuff = {
2727         companyName: 'Sencha Inc.',
2728         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
2729         office: {
2730             size: 40000,
2731             location: 'Redwood City'
2732         }
2733     };
2734
2735     var sencha = Ext.Object.merge(extjs, newStuff);
2736
2737     // extjs and sencha then equals to
2738     {
2739         companyName: 'Sencha Inc.',
2740         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
2741         isSuperCool: true
2742         office: {
2743             size: 30000,
2744             location: 'Redwood City'
2745             isFun: true
2746         }
2747     }
2748
2749      * @param {Object} object,...
2750      * @return {Object} merged The object that is created as a result of merging all the objects passed in.
2751      * @markdown
2752      */
2753     merge: function(source, key, value) {
2754         if (typeof key === 'string') {
2755             if (value && value.constructor === Object) {
2756                 if (source[key] && source[key].constructor === Object) {
2757                     ExtObject.merge(source[key], value);
2758                 }
2759                 else {
2760                     source[key] = Ext.clone(value);
2761                 }
2762             }
2763             else {
2764                 source[key] = value;
2765             }
2766
2767             return source;
2768         }
2769
2770         var i = 1,
2771             ln = arguments.length,
2772             object, property;
2773
2774         for (; i < ln; i++) {
2775             object = arguments[i];
2776
2777             for (property in object) {
2778                 if (object.hasOwnProperty(property)) {
2779                     ExtObject.merge(source, property, object[property]);
2780                 }
2781             }
2782         }
2783
2784         return source;
2785     },
2786
2787     /**
2788      * Returns the first matching key corresponding to the given value.
2789      * If no matching value is found, null is returned.
2790
2791     var person = {
2792         name: 'Jacky',
2793         loves: 'food'
2794     };
2795
2796     alert(Ext.Object.getKey(sencha, 'loves')); // alerts 'food'
2797
2798      * @param {Object} object
2799      * @param {Object} value The value to find
2800      * @markdown
2801      */
2802     getKey: function(object, value) {
2803         for (var property in object) {
2804             if (object.hasOwnProperty(property) && object[property] === value) {
2805                 return property;
2806             }
2807         }
2808
2809         return null;
2810     },
2811
2812     /**
2813      * Gets all values of the given object as an array.
2814
2815     var values = Ext.Object.getValues({
2816         name: 'Jacky',
2817         loves: 'food'
2818     }); // ['Jacky', 'food']
2819
2820      * @param {Object} object
2821      * @return {Array} An array of values from the object
2822      * @markdown
2823      */
2824     getValues: function(object) {
2825         var values = [],
2826             property;
2827
2828         for (property in object) {
2829             if (object.hasOwnProperty(property)) {
2830                 values.push(object[property]);
2831             }
2832         }
2833
2834         return values;
2835     },
2836
2837     /**
2838      * Gets all keys of the given object as an array.
2839
2840     var values = Ext.Object.getKeys({
2841         name: 'Jacky',
2842         loves: 'food'
2843     }); // ['name', 'loves']
2844
2845      * @param {Object} object
2846      * @return {Array} An array of keys from the object
2847      * @method
2848      */
2849     getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
2850         var keys = [],
2851             property;
2852
2853         for (property in object) {
2854             if (object.hasOwnProperty(property)) {
2855                 keys.push(property);
2856             }
2857         }
2858
2859         return keys;
2860     },
2861
2862     /**
2863      * Gets the total number of this object's own properties
2864
2865     var size = Ext.Object.getSize({
2866         name: 'Jacky',
2867         loves: 'food'
2868     }); // size equals 2
2869
2870      * @param {Object} object
2871      * @return {Number} size
2872      * @markdown
2873      */
2874     getSize: function(object) {
2875         var size = 0,
2876             property;
2877
2878         for (property in object) {
2879             if (object.hasOwnProperty(property)) {
2880                 size++;
2881             }
2882         }
2883
2884         return size;
2885     }
2886 };
2887
2888
2889 /**
2890  * A convenient alias method for {@link Ext.Object#merge}
2891  *
2892  * @member Ext
2893  * @method merge
2894  */
2895 Ext.merge = Ext.Object.merge;
2896
2897 /**
2898  * A convenient alias method for {@link Ext.Object#toQueryString}
2899  *
2900  * @member Ext
2901  * @method urlEncode
2902  * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString Ext.Object.toQueryString} instead
2903  */
2904 Ext.urlEncode = function() {
2905     var args = Ext.Array.from(arguments),
2906         prefix = '';
2907
2908     // Support for the old `pre` argument
2909     if ((typeof args[1] === 'string')) {
2910         prefix = args[1] + '&';
2911         args[1] = false;
2912     }
2913
2914     return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
2915 };
2916
2917 /**
2918  * A convenient alias method for {@link Ext.Object#fromQueryString}
2919  *
2920  * @member Ext
2921  * @method urlDecode
2922  * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead
2923  */
2924 Ext.urlDecode = function() {
2925     return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
2926 };
2927
2928 })();
2929
2930 /**
2931  * @class Ext.Date
2932  * A set of useful static methods to deal with date
2933  * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
2934  * this object for convenience
2935  *
2936  * The date parsing and formatting syntax contains a subset of
2937  * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
2938  * supported will provide results equivalent to their PHP versions.
2939  *
2940  * The following is a list of all currently supported formats:
2941  * <pre class="">
2942 Format  Description                                                               Example returned values
2943 ------  -----------------------------------------------------------------------   -----------------------
2944   d     Day of the month, 2 digits with leading zeros                             01 to 31
2945   D     A short textual representation of the day of the week                     Mon to Sun
2946   j     Day of the month without leading zeros                                    1 to 31
2947   l     A full textual representation of the day of the week                      Sunday to Saturday
2948   N     ISO-8601 numeric representation of the day of the week                    1 (for Monday) through 7 (for Sunday)
2949   S     English ordinal suffix for the day of the month, 2 characters             st, nd, rd or th. Works well with j
2950   w     Numeric representation of the day of the week                             0 (for Sunday) to 6 (for Saturday)
2951   z     The day of the year (starting from 0)                                     0 to 364 (365 in leap years)
2952   W     ISO-8601 week number of year, weeks starting on Monday                    01 to 53
2953   F     A full textual representation of a month, such as January or March        January to December
2954   m     Numeric representation of a month, with leading zeros                     01 to 12
2955   M     A short textual representation of a month                                 Jan to Dec
2956   n     Numeric representation of a month, without leading zeros                  1 to 12
2957   t     Number of days in the given month                                         28 to 31
2958   L     Whether it&#39;s a leap year                                                  1 if it is a leap year, 0 otherwise.
2959   o     ISO-8601 year number (identical to (Y), but if the ISO week number (W)    Examples: 1998 or 2004
2960         belongs to the previous or next year, that year is used instead)
2961   Y     A full numeric representation of a year, 4 digits                         Examples: 1999 or 2003
2962   y     A two digit representation of a year                                      Examples: 99 or 03
2963   a     Lowercase Ante meridiem and Post meridiem                                 am or pm
2964   A     Uppercase Ante meridiem and Post meridiem                                 AM or PM
2965   g     12-hour format of an hour without leading zeros                           1 to 12
2966   G     24-hour format of an hour without leading zeros                           0 to 23
2967   h     12-hour format of an hour with leading zeros                              01 to 12
2968   H     24-hour format of an hour with leading zeros                              00 to 23
2969   i     Minutes, with leading zeros                                               00 to 59
2970   s     Seconds, with leading zeros                                               00 to 59
2971   u     Decimal fraction of a second                                              Examples:
2972         (minimum 1 digit, arbitrary number of digits allowed)                     001 (i.e. 0.001s) or
2973                                                                                   100 (i.e. 0.100s) or
2974                                                                                   999 (i.e. 0.999s) or
2975                                                                                   999876543210 (i.e. 0.999876543210s)
2976   O     Difference to Greenwich time (GMT) in hours and minutes                   Example: +1030
2977   P     Difference to Greenwich time (GMT) with colon between hours and minutes   Example: -08:00
2978   T     Timezone abbreviation of the machine running the code                     Examples: EST, MDT, PDT ...
2979   Z     Timezone offset in seconds (negative if west of UTC, positive if east)    -43200 to 50400
2980   c     ISO 8601 date
2981         Notes:                                                                    Examples:
2982         1) If unspecified, the month / day defaults to the current month / day,   1991 or
2983            the time defaults to midnight, while the timezone defaults to the      1992-10 or
2984            browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
2985            and minutes. The "T" delimiter, seconds, milliseconds and timezone     1994-08-19T16:20+01:00 or
2986            are optional.                                                          1995-07-18T17:21:28-02:00 or
2987         2) The decimal fraction of a second, if specified, must contain at        1996-06-17T18:22:29.98765+03:00 or
2988            least 1 digit (there is no limit to the maximum number                 1997-05-16T19:23:30,12345-0400 or
2989            of digits allowed), and may be delimited by either a '.' or a ','      1998-04-15T20:24:31.2468Z or
2990         Refer to the examples on the right for the various levels of              1999-03-14T20:24:32Z or
2991         date-time granularity which are supported, or see                         2000-02-13T21:25:33
2992         http://www.w3.org/TR/NOTE-datetime for more info.                         2001-01-12 22:26:34
2993   U     Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)                1193432466 or -2138434463
2994   MS    Microsoft AJAX serialized dates                                           \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
2995                                                                                   \/Date(1238606590509+0800)\/
2996 </pre>
2997  *
2998  * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
2999  * <pre><code>
3000 // Sample date:
3001 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
3002
3003 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
3004 console.log(Ext.Date.format(dt, 'Y-m-d'));                          // 2007-01-10
3005 console.log(Ext.Date.format(dt, 'F j, Y, g:i a'));                  // January 10, 2007, 3:05 pm
3006 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
3007 </code></pre>
3008  *
3009  * Here are some standard date/time patterns that you might find helpful.  They
3010  * are not part of the source of Ext.Date, but to use them you can simply copy this
3011  * block of code into any script that is included after Ext.Date and they will also become
3012  * globally available on the Date object.  Feel free to add or remove patterns as needed in your code.
3013  * <pre><code>
3014 Ext.Date.patterns = {
3015     ISO8601Long:"Y-m-d H:i:s",
3016     ISO8601Short:"Y-m-d",
3017     ShortDate: "n/j/Y",
3018     LongDate: "l, F d, Y",
3019     FullDateTime: "l, F d, Y g:i:s A",
3020     MonthDay: "F d",
3021     ShortTime: "g:i A",
3022     LongTime: "g:i:s A",
3023     SortableDateTime: "Y-m-d\\TH:i:s",
3024     UniversalSortableDateTime: "Y-m-d H:i:sO",
3025     YearMonth: "F, Y"
3026 };
3027 </code></pre>
3028  *
3029  * Example usage:
3030  * <pre><code>
3031 var dt = new Date();
3032 console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
3033 </code></pre>
3034  * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
3035  * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
3036  * @singleton
3037  */
3038
3039 /*
3040  * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
3041  * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
3042  * They generate precompiled functions from format patterns instead of parsing and
3043  * processing each pattern every time a date is formatted. These functions are available
3044  * on every Date object.
3045  */
3046
3047 (function() {
3048
3049 // create private copy of Ext's Ext.util.Format.format() method
3050 // - to remove unnecessary dependency
3051 // - to resolve namespace conflict with MS-Ajax's implementation
3052 function xf(format) {
3053     var args = Array.prototype.slice.call(arguments, 1);
3054     return format.replace(/\{(\d+)\}/g, function(m, i) {
3055         return args[i];
3056     });
3057 }
3058
3059 Ext.Date = {
3060     /**
3061      * Returns the current timestamp
3062      * @return {Date} The current timestamp
3063      * @method
3064      */
3065     now: Date.now || function() {
3066         return +new Date();
3067     },
3068
3069     /**
3070      * @private
3071      * Private for now
3072      */
3073     toString: function(date) {
3074         var pad = Ext.String.leftPad;
3075
3076         return date.getFullYear() + "-"
3077             + pad(date.getMonth() + 1, 2, '0') + "-"
3078             + pad(date.getDate(), 2, '0') + "T"
3079             + pad(date.getHours(), 2, '0') + ":"
3080             + pad(date.getMinutes(), 2, '0') + ":"
3081             + pad(date.getSeconds(), 2, '0');
3082     },
3083
3084     /**
3085      * Returns the number of milliseconds between two dates
3086      * @param {Date} dateA The first date
3087      * @param {Date} dateB (optional) The second date, defaults to now
3088      * @return {Number} The difference in milliseconds
3089      */
3090     getElapsed: function(dateA, dateB) {
3091         return Math.abs(dateA - (dateB || new Date()));
3092     },
3093
3094     /**
3095      * Global flag which determines if strict date parsing should be used.
3096      * Strict date parsing will not roll-over invalid dates, which is the
3097      * default behaviour of javascript Date objects.
3098      * (see {@link #parse} for more information)
3099      * Defaults to <tt>false</tt>.
3100      * @static
3101      * @type Boolean
3102     */
3103     useStrict: false,
3104
3105     // private
3106     formatCodeToRegex: function(character, currentGroup) {
3107         // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
3108         var p = utilDate.parseCodes[character];
3109
3110         if (p) {
3111           p = typeof p == 'function'? p() : p;
3112           utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
3113         }
3114
3115         return p ? Ext.applyIf({
3116           c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
3117         }, p) : {
3118             g: 0,
3119             c: null,
3120             s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
3121         };
3122     },
3123
3124     /**
3125      * <p>An object hash in which each property is a date parsing function. The property name is the
3126      * format string which that function parses.</p>
3127      * <p>This object is automatically populated with date parsing functions as
3128      * date formats are requested for Ext standard formatting strings.</p>
3129      * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
3130      * may be used as a format string to {@link #parse}.<p>
3131      * <p>Example:</p><pre><code>
3132 Ext.Date.parseFunctions['x-date-format'] = myDateParser;
3133 </code></pre>
3134      * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
3135      * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
3136      * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
3137      * (i.e. prevent javascript Date "rollover") (The default must be false).
3138      * Invalid date strings should return null when parsed.</div></li>
3139      * </ul></div></p>
3140      * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
3141      * formatting function must be placed into the {@link #formatFunctions} property.
3142      * @property parseFunctions
3143      * @static
3144      * @type Object
3145      */
3146     parseFunctions: {
3147         "MS": function(input, strict) {
3148             // note: the timezone offset is ignored since the MS Ajax server sends
3149             // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
3150             var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
3151             var r = (input || '').match(re);
3152             return r? new Date(((r[1] || '') + r[2]) * 1) : null;
3153         }
3154     },
3155     parseRegexes: [],
3156
3157     /**
3158      * <p>An object hash in which each property is a date formatting function. The property name is the
3159      * format string which corresponds to the produced formatted date string.</p>
3160      * <p>This object is automatically populated with date formatting functions as
3161      * date formats are requested for Ext standard formatting strings.</p>
3162      * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
3163      * may be used as a format string to {@link #format}. Example:</p><pre><code>
3164 Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
3165 </code></pre>
3166      * <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>
3167      * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
3168      * </ul></div></p>
3169      * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
3170      * parsing function must be placed into the {@link #parseFunctions} property.
3171      * @property formatFunctions
3172      * @static
3173      * @type Object
3174      */
3175     formatFunctions: {
3176         "MS": function() {
3177             // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
3178             return '\\/Date(' + this.getTime() + ')\\/';
3179         }
3180     },
3181
3182     y2kYear : 50,
3183
3184     /**
3185      * Date interval constant
3186      * @static
3187      * @type String
3188      */
3189     MILLI : "ms",
3190
3191     /**
3192      * Date interval constant
3193      * @static
3194      * @type String
3195      */
3196     SECOND : "s",
3197
3198     /**
3199      * Date interval constant
3200      * @static
3201      * @type String
3202      */
3203     MINUTE : "mi",
3204
3205     /** Date interval constant
3206      * @static
3207      * @type String
3208      */
3209     HOUR : "h",
3210
3211     /**
3212      * Date interval constant
3213      * @static
3214      * @type String
3215      */
3216     DAY : "d",
3217
3218     /**
3219      * Date interval constant
3220      * @static
3221      * @type String
3222      */
3223     MONTH : "mo",
3224
3225     /**
3226      * Date interval constant
3227      * @static
3228      * @type String
3229      */
3230     YEAR : "y",
3231
3232     /**
3233      * <p>An object hash containing default date values used during date parsing.</p>
3234      * <p>The following properties are available:<div class="mdetail-params"><ul>
3235      * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
3236      * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
3237      * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
3238      * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
3239      * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
3240      * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
3241      * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
3242      * </ul></div></p>
3243      * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
3244      * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
3245      * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
3246      * It is the responsiblity of the developer to account for this.</b></p>
3247      * Example Usage:
3248      * <pre><code>
3249 // set default day value to the first day of the month
3250 Ext.Date.defaults.d = 1;
3251
3252 // parse a February date string containing only year and month values.
3253 // setting the default day value to 1 prevents weird date rollover issues
3254 // when attempting to parse the following date string on, for example, March 31st 2009.
3255 Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
3256 </code></pre>
3257      * @property defaults
3258      * @static
3259      * @type Object
3260      */
3261     defaults: {},
3262
3263     /**
3264      * An array of textual day names.
3265      * Override these values for international dates.
3266      * Example:
3267      * <pre><code>
3268 Ext.Date.dayNames = [
3269     'SundayInYourLang',
3270     'MondayInYourLang',
3271     ...
3272 ];
3273 </code></pre>
3274      * @type Array
3275      * @static
3276      */
3277     dayNames : [
3278         "Sunday",
3279         "Monday",
3280         "Tuesday",
3281         "Wednesday",
3282         "Thursday",
3283         "Friday",
3284         "Saturday"
3285     ],
3286
3287     /**
3288      * An array of textual month names.
3289      * Override these values for international dates.
3290      * Example:
3291      * <pre><code>
3292 Ext.Date.monthNames = [
3293     'JanInYourLang',
3294     'FebInYourLang',
3295     ...
3296 ];
3297 </code></pre>
3298      * @type Array
3299      * @static
3300      */
3301     monthNames : [
3302         "January",
3303         "February",
3304         "March",
3305         "April",
3306         "May",
3307         "June",
3308         "July",
3309         "August",
3310         "September",
3311         "October",
3312         "November",
3313         "December"
3314     ],
3315
3316     /**
3317      * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
3318      * Override these values for international dates.
3319      * Example:
3320      * <pre><code>
3321 Ext.Date.monthNumbers = {
3322     'ShortJanNameInYourLang':0,
3323     'ShortFebNameInYourLang':1,
3324     ...
3325 };
3326 </code></pre>
3327      * @type Object
3328      * @static
3329      */
3330     monthNumbers : {
3331         Jan:0,
3332         Feb:1,
3333         Mar:2,
3334         Apr:3,
3335         May:4,
3336         Jun:5,
3337         Jul:6,
3338         Aug:7,
3339         Sep:8,
3340         Oct:9,
3341         Nov:10,
3342         Dec:11
3343     },
3344     /**
3345      * <p>The date format string that the {@link #dateRenderer} and {@link #date} functions use.
3346      * see {@link #Date} for details.</p>
3347      * <p>This defaults to <code>m/d/Y</code>, but may be overridden in a locale file.</p>
3348      * @property defaultFormat
3349      * @static
3350      * @type String
3351      */
3352     defaultFormat : "m/d/Y",
3353     /**
3354      * Get the short month name for the given month number.
3355      * Override this function for international dates.
3356      * @param {Number} month A zero-based javascript month number.
3357      * @return {String} The short month name.
3358      * @static
3359      */
3360     getShortMonthName : function(month) {
3361         return utilDate.monthNames[month].substring(0, 3);
3362     },
3363
3364     /**
3365      * Get the short day name for the given day number.
3366      * Override this function for international dates.
3367      * @param {Number} day A zero-based javascript day number.
3368      * @return {String} The short day name.
3369      * @static
3370      */
3371     getShortDayName : function(day) {
3372         return utilDate.dayNames[day].substring(0, 3);
3373     },
3374
3375     /**
3376      * Get the zero-based javascript month number for the given short/full month name.
3377      * Override this function for international dates.
3378      * @param {String} name The short/full month name.
3379      * @return {Number} The zero-based javascript month number.
3380      * @static
3381      */
3382     getMonthNumber : function(name) {
3383         // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
3384         return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
3385     },
3386
3387     /**
3388      * Checks if the specified format contains hour information
3389      * @param {String} format The format to check
3390      * @return {Boolean} True if the format contains hour information
3391      * @static
3392      * @method
3393      */
3394     formatContainsHourInfo : (function(){
3395         var stripEscapeRe = /(\\.)/g,
3396             hourInfoRe = /([gGhHisucUOPZ]|MS)/;
3397         return function(format){
3398             return hourInfoRe.test(format.replace(stripEscapeRe, ''));
3399         };
3400     })(),
3401
3402     /**
3403      * Checks if the specified format contains information about
3404      * anything other than the time.
3405      * @param {String} format The format to check
3406      * @return {Boolean} True if the format contains information about
3407      * date/day information.
3408      * @static
3409      * @method
3410      */
3411     formatContainsDateInfo : (function(){
3412         var stripEscapeRe = /(\\.)/g,
3413             dateInfoRe = /([djzmnYycU]|MS)/;
3414
3415         return function(format){
3416             return dateInfoRe.test(format.replace(stripEscapeRe, ''));
3417         };
3418     })(),
3419
3420     /**
3421      * The base format-code to formatting-function hashmap used by the {@link #format} method.
3422      * Formatting functions are strings (or functions which return strings) which
3423      * will return the appropriate value when evaluated in the context of the Date object
3424      * from which the {@link #format} method is called.
3425      * Add to / override these mappings for custom date formatting.
3426      * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
3427      * Example:
3428      * <pre><code>
3429 Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
3430 console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
3431 </code></pre>
3432      * @type Object
3433      * @static
3434      */
3435     formatCodes : {
3436         d: "Ext.String.leftPad(this.getDate(), 2, '0')",
3437         D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
3438         j: "this.getDate()",
3439         l: "Ext.Date.dayNames[this.getDay()]",
3440         N: "(this.getDay() ? this.getDay() : 7)",
3441         S: "Ext.Date.getSuffix(this)",
3442         w: "this.getDay()",
3443         z: "Ext.Date.getDayOfYear(this)",
3444         W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
3445         F: "Ext.Date.monthNames[this.getMonth()]",
3446         m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
3447         M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
3448         n: "(this.getMonth() + 1)",
3449         t: "Ext.Date.getDaysInMonth(this)",
3450         L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
3451         o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
3452         Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
3453         y: "('' + this.getFullYear()).substring(2, 4)",
3454         a: "(this.getHours() < 12 ? 'am' : 'pm')",
3455         A: "(this.getHours() < 12 ? 'AM' : 'PM')",
3456         g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
3457         G: "this.getHours()",
3458         h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
3459         H: "Ext.String.leftPad(this.getHours(), 2, '0')",
3460         i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
3461         s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
3462         u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
3463         O: "Ext.Date.getGMTOffset(this)",
3464         P: "Ext.Date.getGMTOffset(this, true)",
3465         T: "Ext.Date.getTimezone(this)",
3466         Z: "(this.getTimezoneOffset() * -60)",
3467
3468         c: function() { // ISO-8601 -- GMT format
3469             for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
3470                 var e = c.charAt(i);
3471                 code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
3472             }
3473             return code.join(" + ");
3474         },
3475         /*
3476         c: function() { // ISO-8601 -- UTC format
3477             return [
3478               "this.getUTCFullYear()", "'-'",
3479               "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
3480               "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
3481               "'T'",
3482               "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
3483               "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
3484               "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
3485               "'Z'"
3486             ].join(" + ");
3487         },
3488         */
3489
3490         U: "Math.round(this.getTime() / 1000)"
3491     },
3492
3493     /**
3494      * Checks if the passed Date parameters will cause a javascript Date "rollover".
3495      * @param {Number} year 4-digit year
3496      * @param {Number} month 1-based month-of-year
3497      * @param {Number} day Day of month
3498      * @param {Number} hour (optional) Hour
3499      * @param {Number} minute (optional) Minute
3500      * @param {Number} second (optional) Second
3501      * @param {Number} millisecond (optional) Millisecond
3502      * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
3503      * @static
3504      */
3505     isValid : function(y, m, d, h, i, s, ms) {
3506         // setup defaults
3507         h = h || 0;
3508         i = i || 0;
3509         s = s || 0;
3510         ms = ms || 0;
3511
3512         // Special handling for year < 100
3513         var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
3514
3515         return y == dt.getFullYear() &&
3516             m == dt.getMonth() + 1 &&
3517             d == dt.getDate() &&
3518             h == dt.getHours() &&
3519             i == dt.getMinutes() &&
3520             s == dt.getSeconds() &&
3521             ms == dt.getMilliseconds();
3522     },
3523
3524     /**
3525      * Parses the passed string using the specified date format.
3526      * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
3527      * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
3528      * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
3529      * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
3530      * Keep in mind that the input date string must precisely match the specified format string
3531      * in order for the parse operation to be successful (failed parse operations return a null value).
3532      * <p>Example:</p><pre><code>
3533 //dt = Fri May 25 2007 (current date)
3534 var dt = new Date();
3535
3536 //dt = Thu May 25 2006 (today&#39;s month/day in 2006)
3537 dt = Ext.Date.parse("2006", "Y");
3538
3539 //dt = Sun Jan 15 2006 (all date parts specified)
3540 dt = Ext.Date.parse("2006-01-15", "Y-m-d");
3541
3542 //dt = Sun Jan 15 2006 15:20:01
3543 dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
3544
3545 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
3546 dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
3547 </code></pre>
3548      * @param {String} input The raw date string.
3549      * @param {String} format The expected date string format.
3550      * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
3551                         (defaults to false). Invalid date strings will return null when parsed.
3552      * @return {Date} The parsed Date.
3553      * @static
3554      */
3555     parse : function(input, format, strict) {
3556         var p = utilDate.parseFunctions;
3557         if (p[format] == null) {
3558             utilDate.createParser(format);
3559         }
3560         return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
3561     },
3562
3563     // Backwards compat
3564     parseDate: function(input, format, strict){
3565         return utilDate.parse(input, format, strict);
3566     },
3567
3568
3569     // private
3570     getFormatCode : function(character) {
3571         var f = utilDate.formatCodes[character];
3572
3573         if (f) {
3574           f = typeof f == 'function'? f() : f;
3575           utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
3576         }
3577
3578         // note: unknown characters are treated as literals
3579         return f || ("'" + Ext.String.escape(character) + "'");
3580     },
3581
3582     // private
3583     createFormat : function(format) {
3584         var code = [],
3585             special = false,
3586             ch = '';
3587
3588         for (var i = 0; i < format.length; ++i) {
3589             ch = format.charAt(i);
3590             if (!special && ch == "\\") {
3591                 special = true;
3592             } else if (special) {
3593                 special = false;
3594                 code.push("'" + Ext.String.escape(ch) + "'");
3595             } else {
3596                 code.push(utilDate.getFormatCode(ch));
3597             }
3598         }
3599         utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
3600     },
3601
3602     // private
3603     createParser : (function() {
3604         var code = [
3605             "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
3606                 "def = Ext.Date.defaults,",
3607                 "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
3608
3609             "if(results){",
3610                 "{1}",
3611
3612                 "if(u != null){", // i.e. unix time is defined
3613                     "v = new Date(u * 1000);", // give top priority to UNIX time
3614                 "}else{",
3615                     // create Date object representing midnight of the current day;
3616                     // this will provide us with our date defaults
3617                     // (note: clearTime() handles Daylight Saving Time automatically)
3618                     "dt = Ext.Date.clearTime(new Date);",
3619
3620                     // date calculations (note: these calculations create a dependency on Ext.Number.from())
3621                     "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
3622                     "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
3623                     "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
3624
3625                     // time calculations (note: these calculations create a dependency on Ext.Number.from())
3626                     "h  = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
3627                     "i  = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
3628                     "s  = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
3629                     "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
3630
3631                     "if(z >= 0 && y >= 0){",
3632                         // both the year and zero-based day of year are defined and >= 0.
3633                         // these 2 values alone provide sufficient info to create a full date object
3634
3635                         // create Date object representing January 1st for the given year
3636                         // handle years < 100 appropriately
3637                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3638
3639                         // then add day of year, checking for Date "rollover" if necessary
3640                         "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
3641                     "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
3642                         "v = null;", // invalid date, so return null
3643                     "}else{",
3644                         // plain old Date object
3645                         // handle years < 100 properly
3646                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3647                     "}",
3648                 "}",
3649             "}",
3650
3651             "if(v){",
3652                 // favour UTC offset over GMT offset
3653                 "if(zz != null){",
3654                     // reset to UTC, then add offset
3655                     "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
3656                 "}else if(o){",
3657                     // reset to GMT, then add offset
3658                     "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
3659                 "}",
3660             "}",
3661
3662             "return v;"
3663         ].join('\n');
3664
3665         return function(format) {
3666             var regexNum = utilDate.parseRegexes.length,
3667                 currentGroup = 1,
3668                 calc = [],
3669                 regex = [],
3670                 special = false,
3671                 ch = "";
3672
3673             for (var i = 0; i < format.length; ++i) {
3674                 ch = format.charAt(i);
3675                 if (!special && ch == "\\") {
3676                     special = true;
3677                 } else if (special) {
3678                     special = false;
3679                     regex.push(Ext.String.escape(ch));
3680                 } else {
3681                     var obj = utilDate.formatCodeToRegex(ch, currentGroup);
3682                     currentGroup += obj.g;
3683                     regex.push(obj.s);
3684                     if (obj.g && obj.c) {
3685                         calc.push(obj.c);
3686                     }
3687                 }
3688             }
3689
3690             utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
3691             utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
3692         };
3693     })(),
3694
3695     // private
3696     parseCodes : {
3697         /*
3698          * Notes:
3699          * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
3700          * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
3701          * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
3702          */
3703         d: {
3704             g:1,
3705             c:"d = parseInt(results[{0}], 10);\n",
3706             s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
3707         },
3708         j: {
3709             g:1,
3710             c:"d = parseInt(results[{0}], 10);\n",
3711             s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
3712         },
3713         D: function() {
3714             for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
3715             return {
3716                 g:0,
3717                 c:null,
3718                 s:"(?:" + a.join("|") +")"
3719             };
3720         },
3721         l: function() {
3722             return {
3723                 g:0,
3724                 c:null,
3725                 s:"(?:" + utilDate.dayNames.join("|") + ")"
3726             };
3727         },
3728         N: {
3729             g:0,
3730             c:null,
3731             s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
3732         },
3733         S: {
3734             g:0,
3735             c:null,
3736             s:"(?:st|nd|rd|th)"
3737         },
3738         w: {
3739             g:0,
3740             c:null,
3741             s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
3742         },
3743         z: {
3744             g:1,
3745             c:"z = parseInt(results[{0}], 10);\n",
3746             s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
3747         },
3748         W: {
3749             g:0,
3750             c:null,
3751             s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
3752         },
3753         F: function() {
3754             return {
3755                 g:1,
3756                 c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
3757                 s:"(" + utilDate.monthNames.join("|") + ")"
3758             };
3759         },
3760         M: function() {
3761             for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
3762             return Ext.applyIf({
3763                 s:"(" + a.join("|") + ")"
3764             }, utilDate.formatCodeToRegex("F"));
3765         },
3766         m: {
3767             g:1,
3768             c:"m = parseInt(results[{0}], 10) - 1;\n",
3769             s:"(\\d{2})" // month number with leading zeros (01 - 12)
3770         },
3771         n: {
3772             g:1,
3773             c:"m = parseInt(results[{0}], 10) - 1;\n",
3774             s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
3775         },
3776         t: {
3777             g:0,
3778             c:null,
3779             s:"(?:\\d{2})" // no. of days in the month (28 - 31)
3780         },
3781         L: {
3782             g:0,
3783             c:null,
3784             s:"(?:1|0)"
3785         },
3786         o: function() {
3787             return utilDate.formatCodeToRegex("Y");
3788         },
3789         Y: {
3790             g:1,
3791             c:"y = parseInt(results[{0}], 10);\n",
3792             s:"(\\d{4})" // 4-digit year
3793         },
3794         y: {
3795             g:1,
3796             c:"var ty = parseInt(results[{0}], 10);\n"
3797                 + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
3798             s:"(\\d{1,2})"
3799         },
3800         /*
3801          * In the am/pm parsing routines, we allow both upper and lower case
3802          * even though it doesn't exactly match the spec. It gives much more flexibility
3803          * in being able to specify case insensitive regexes.
3804          */
3805         a: {
3806             g:1,
3807             c:"if (/(am)/i.test(results[{0}])) {\n"
3808                 + "if (!h || h == 12) { h = 0; }\n"
3809                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
3810             s:"(am|pm|AM|PM)"
3811         },
3812         A: {
3813             g:1,
3814             c:"if (/(am)/i.test(results[{0}])) {\n"
3815                 + "if (!h || h == 12) { h = 0; }\n"
3816                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
3817             s:"(AM|PM|am|pm)"
3818         },
3819         g: function() {
3820             return utilDate.formatCodeToRegex("G");
3821         },
3822         G: {
3823             g:1,
3824             c:"h = parseInt(results[{0}], 10);\n",
3825             s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
3826         },
3827         h: function() {
3828             return utilDate.formatCodeToRegex("H");
3829         },
3830         H: {
3831             g:1,
3832             c:"h = parseInt(results[{0}], 10);\n",
3833             s:"(\\d{2})" //  24-hr format of an hour with leading zeroes (00 - 23)
3834         },
3835         i: {
3836             g:1,
3837             c:"i = parseInt(results[{0}], 10);\n",
3838             s:"(\\d{2})" // minutes with leading zeros (00 - 59)
3839         },
3840         s: {
3841             g:1,
3842             c:"s = parseInt(results[{0}], 10);\n",
3843             s:"(\\d{2})" // seconds with leading zeros (00 - 59)
3844         },
3845         u: {
3846             g:1,
3847             c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
3848             s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
3849         },
3850         O: {
3851             g:1,
3852             c:[
3853                 "o = results[{0}];",
3854                 "var sn = o.substring(0,1),", // get + / - sign
3855                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
3856                     "mn = o.substring(3,5) % 60;", // get minutes
3857                 "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
3858             ].join("\n"),
3859             s: "([+\-]\\d{4})" // GMT offset in hrs and mins
3860         },
3861         P: {
3862             g:1,
3863             c:[
3864                 "o = results[{0}];",
3865                 "var sn = o.substring(0,1),", // get + / - sign
3866                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
3867                     "mn = o.substring(4,6) % 60;", // get minutes
3868                 "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
3869             ].join("\n"),
3870             s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
3871         },
3872         T: {
3873             g:0,
3874             c:null,
3875             s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
3876         },
3877         Z: {
3878             g:1,
3879             c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
3880                   + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
3881             s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
3882         },
3883         c: function() {
3884             var calc = [],
3885                 arr = [
3886                     utilDate.formatCodeToRegex("Y", 1), // year
3887                     utilDate.formatCodeToRegex("m", 2), // month
3888                     utilDate.formatCodeToRegex("d", 3), // day
3889                     utilDate.formatCodeToRegex("h", 4), // hour
3890                     utilDate.formatCodeToRegex("i", 5), // minute
3891                     utilDate.formatCodeToRegex("s", 6), // second
3892                     {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)
3893                     {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
3894                         "if(results[8]) {", // timezone specified
3895                             "if(results[8] == 'Z'){",
3896                                 "zz = 0;", // UTC
3897                             "}else if (results[8].indexOf(':') > -1){",
3898                                 utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
3899                             "}else{",
3900                                 utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
3901                             "}",
3902                         "}"
3903                     ].join('\n')}
3904                 ];
3905
3906             for (var i = 0, l = arr.length; i < l; ++i) {
3907                 calc.push(arr[i].c);
3908             }
3909
3910             return {
3911                 g:1,
3912                 c:calc.join(""),
3913                 s:[
3914                     arr[0].s, // year (required)
3915                     "(?:", "-", arr[1].s, // month (optional)
3916                         "(?:", "-", arr[2].s, // day (optional)
3917                             "(?:",
3918                                 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
3919                                 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
3920                                 "(?::", arr[5].s, ")?", // seconds (optional)
3921                                 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
3922                                 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
3923                             ")?",
3924                         ")?",
3925                     ")?"
3926                 ].join("")
3927             };
3928         },
3929         U: {
3930             g:1,
3931             c:"u = parseInt(results[{0}], 10);\n",
3932             s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
3933         }
3934     },
3935
3936     //Old Ext.Date prototype methods.
3937     // private
3938     dateFormat: function(date, format) {
3939         return utilDate.format(date, format);
3940     },
3941
3942     /**
3943      * Formats a date given the supplied format string.
3944      * @param {Date} date The date to format
3945      * @param {String} format The format string
3946      * @return {String} The formatted date
3947      */
3948     format: function(date, format) {
3949         if (utilDate.formatFunctions[format] == null) {
3950             utilDate.createFormat(format);
3951         }
3952         var result = utilDate.formatFunctions[format].call(date);
3953         return result + '';
3954     },
3955
3956     /**
3957      * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
3958      *
3959      * Note: The date string returned by the javascript Date object's toString() method varies
3960      * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
3961      * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
3962      * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
3963      * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
3964      * from the GMT offset portion of the date string.
3965      * @param {Date} date The date
3966      * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
3967      */
3968     getTimezone : function(date) {
3969         // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
3970         //
3971         // Opera  : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
3972         // 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)
3973         // FF     : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
3974         // IE     : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
3975         // IE     : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
3976         //
3977         // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
3978         // step 1: (?:\((.*)\) -- find timezone in parentheses
3979         // 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
3980         // step 3: remove all non uppercase characters found in step 1 and 2
3981         return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
3982     },
3983
3984     /**
3985      * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
3986      * @param {Date} date The date
3987      * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
3988      * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
3989      */
3990     getGMTOffset : function(date, colon) {
3991         var offset = date.getTimezoneOffset();
3992         return (offset > 0 ? "-" : "+")
3993             + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
3994             + (colon ? ":" : "")
3995             + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
3996     },
3997
3998     /**
3999      * Get the numeric day number of the year, adjusted for leap year.
4000      * @param {Date} date The date
4001      * @return {Number} 0 to 364 (365 in leap years).
4002      */
4003     getDayOfYear: function(date) {
4004         var num = 0,
4005             d = Ext.Date.clone(date),
4006             m = date.getMonth(),
4007             i;
4008
4009         for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
4010             num += utilDate.getDaysInMonth(d);
4011         }
4012         return num + date.getDate() - 1;
4013     },
4014
4015     /**
4016      * Get the numeric ISO-8601 week number of the year.
4017      * (equivalent to the format specifier 'W', but without a leading zero).
4018      * @param {Date} date The date
4019      * @return {Number} 1 to 53
4020      * @method
4021      */
4022     getWeekOfYear : (function() {
4023         // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
4024         var ms1d = 864e5, // milliseconds in a day
4025             ms7d = 7 * ms1d; // milliseconds in a week
4026
4027         return function(date) { // return a closure so constants get calculated only once
4028             var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
4029                 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
4030                 Wyr = new Date(AWN * ms7d).getUTCFullYear();
4031
4032             return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
4033         };
4034     })(),
4035
4036     /**
4037      * Checks if the current date falls within a leap year.
4038      * @param {Date} date The date
4039      * @return {Boolean} True if the current date falls within a leap year, false otherwise.
4040      */
4041     isLeapYear : function(date) {
4042         var year = date.getFullYear();
4043         return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
4044     },
4045
4046     /**
4047      * Get the first day of the current month, adjusted for leap year.  The returned value
4048      * is the numeric day index within the week (0-6) which can be used in conjunction with
4049      * the {@link #monthNames} array to retrieve the textual day name.
4050      * Example:
4051      * <pre><code>
4052 var dt = new Date('1/10/2007'),
4053     firstDay = Ext.Date.getFirstDayOfMonth(dt);
4054 console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
4055      * </code></pre>
4056      * @param {Date} date The date
4057      * @return {Number} The day number (0-6).
4058      */
4059     getFirstDayOfMonth : function(date) {
4060         var day = (date.getDay() - (date.getDate() - 1)) % 7;
4061         return (day < 0) ? (day + 7) : day;
4062     },
4063
4064     /**
4065      * Get the last day of the current month, adjusted for leap year.  The returned value
4066      * is the numeric day index within the week (0-6) which can be used in conjunction with
4067      * the {@link #monthNames} array to retrieve the textual day name.
4068      * Example:
4069      * <pre><code>
4070 var dt = new Date('1/10/2007'),
4071     lastDay = Ext.Date.getLastDayOfMonth(dt);
4072 console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
4073      * </code></pre>
4074      * @param {Date} date The date
4075      * @return {Number} The day number (0-6).
4076      */
4077     getLastDayOfMonth : function(date) {
4078         return utilDate.getLastDateOfMonth(date).getDay();
4079     },
4080
4081
4082     /**
4083      * Get the date of the first day of the month in which this date resides.
4084      * @param {Date} date The date
4085      * @return {Date}
4086      */
4087     getFirstDateOfMonth : function(date) {
4088         return new Date(date.getFullYear(), date.getMonth(), 1);
4089     },
4090
4091     /**
4092      * Get the date of the last day of the month in which this date resides.
4093      * @param {Date} date The date
4094      * @return {Date}
4095      */
4096     getLastDateOfMonth : function(date) {
4097         return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
4098     },
4099
4100     /**
4101      * Get the number of days in the current month, adjusted for leap year.
4102      * @param {Date} date The date
4103      * @return {Number} The number of days in the month.
4104      * @method
4105      */
4106     getDaysInMonth: (function() {
4107         var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4108
4109         return function(date) { // return a closure for efficiency
4110             var m = date.getMonth();
4111
4112             return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
4113         };
4114     })(),
4115
4116     /**
4117      * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
4118      * @param {Date} date The date
4119      * @return {String} 'st, 'nd', 'rd' or 'th'.
4120      */
4121     getSuffix : function(date) {
4122         switch (date.getDate()) {
4123             case 1:
4124             case 21:
4125             case 31:
4126                 return "st";
4127             case 2:
4128             case 22:
4129                 return "nd";
4130             case 3:
4131             case 23:
4132                 return "rd";
4133             default:
4134                 return "th";
4135         }
4136     },
4137
4138     /**
4139      * Creates and returns a new Date instance with the exact same date value as the called instance.
4140      * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
4141      * variable will also be changed.  When the intention is to create a new variable that will not
4142      * modify the original instance, you should create a clone.
4143      *
4144      * Example of correctly cloning a date:
4145      * <pre><code>
4146 //wrong way:
4147 var orig = new Date('10/1/2006');
4148 var copy = orig;
4149 copy.setDate(5);
4150 console.log(orig);  //returns 'Thu Oct 05 2006'!
4151
4152 //correct way:
4153 var orig = new Date('10/1/2006'),
4154     copy = Ext.Date.clone(orig);
4155 copy.setDate(5);
4156 console.log(orig);  //returns 'Thu Oct 01 2006'
4157      * </code></pre>
4158      * @param {Date} date The date
4159      * @return {Date} The new Date instance.
4160      */
4161     clone : function(date) {
4162         return new Date(date.getTime());
4163     },
4164
4165     /**
4166      * Checks if the current date is affected by Daylight Saving Time (DST).
4167      * @param {Date} date The date
4168      * @return {Boolean} True if the current date is affected by DST.
4169      */
4170     isDST : function(date) {
4171         // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
4172         // courtesy of @geoffrey.mcgill
4173         return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
4174     },
4175
4176     /**
4177      * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
4178      * automatically adjusting for Daylight Saving Time (DST) where applicable.
4179      * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
4180      * @param {Date} date The date
4181      * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
4182      * @return {Date} this or the clone.
4183      */
4184     clearTime : function(date, clone) {
4185         if (clone) {
4186             return Ext.Date.clearTime(Ext.Date.clone(date));
4187         }
4188
4189         // get current date before clearing time
4190         var d = date.getDate();
4191
4192         // clear time
4193         date.setHours(0);
4194         date.setMinutes(0);
4195         date.setSeconds(0);
4196         date.setMilliseconds(0);
4197
4198         if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
4199             // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
4200             // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
4201
4202             // increment hour until cloned date == current date
4203             for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
4204
4205             date.setDate(d);
4206             date.setHours(c.getHours());
4207         }
4208
4209         return date;
4210     },
4211
4212     /**
4213      * Provides a convenient method for performing basic date arithmetic. This method
4214      * does not modify the Date instance being called - it creates and returns
4215      * a new Date instance containing the resulting date value.
4216      *
4217      * Examples:
4218      * <pre><code>
4219 // Basic usage:
4220 var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
4221 console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
4222
4223 // Negative values will be subtracted:
4224 var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
4225 console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
4226
4227      * </code></pre>
4228      *
4229      * @param {Date} date The date to modify
4230      * @param {String} interval A valid date interval enum value.
4231      * @param {Number} value The amount to add to the current date.
4232      * @return {Date} The new Date instance.
4233      */
4234     add : function(date, interval, value) {
4235         var d = Ext.Date.clone(date),
4236             Date = Ext.Date;
4237         if (!interval || value === 0) return d;
4238
4239         switch(interval.toLowerCase()) {
4240             case Ext.Date.MILLI:
4241                 d.setMilliseconds(d.getMilliseconds() + value);
4242                 break;
4243             case Ext.Date.SECOND:
4244                 d.setSeconds(d.getSeconds() + value);
4245                 break;
4246             case Ext.Date.MINUTE:
4247                 d.setMinutes(d.getMinutes() + value);
4248                 break;
4249             case Ext.Date.HOUR:
4250                 d.setHours(d.getHours() + value);
4251                 break;
4252             case Ext.Date.DAY:
4253                 d.setDate(d.getDate() + value);
4254                 break;
4255             case Ext.Date.MONTH:
4256                 var day = date.getDate();
4257                 if (day > 28) {
4258                     day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
4259                 }
4260                 d.setDate(day);
4261                 d.setMonth(date.getMonth() + value);
4262                 break;
4263             case Ext.Date.YEAR:
4264                 d.setFullYear(date.getFullYear() + value);
4265                 break;
4266         }
4267         return d;
4268     },
4269
4270     /**
4271      * Checks if a date falls on or between the given start and end dates.
4272      * @param {Date} date The date to check
4273      * @param {Date} start Start date
4274      * @param {Date} end End date
4275      * @return {Boolean} true if this date falls on or between the given start and end dates.
4276      */
4277     between : function(date, start, end) {
4278         var t = date.getTime();
4279         return start.getTime() <= t && t <= end.getTime();
4280     },
4281
4282     //Maintains compatibility with old static and prototype window.Date methods.
4283     compat: function() {
4284         var nativeDate = window.Date,
4285             p, u,
4286             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'],
4287             proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'];
4288
4289         //Append statics
4290         Ext.Array.forEach(statics, function(s) {
4291             nativeDate[s] = utilDate[s];
4292         });
4293
4294         //Append to prototype
4295         Ext.Array.forEach(proto, function(s) {
4296             nativeDate.prototype[s] = function() {
4297                 var args = Array.prototype.slice.call(arguments);
4298                 args.unshift(this);
4299                 return utilDate[s].apply(utilDate, args);
4300             };
4301         });
4302     }
4303 };
4304
4305 var utilDate = Ext.Date;
4306
4307 })();
4308
4309 /**
4310  * @author Jacky Nguyen <jacky@sencha.com>
4311  * @docauthor Jacky Nguyen <jacky@sencha.com>
4312  * @class Ext.Base
4313  *
4314  * The root of all classes created with {@link Ext#define}
4315  * All prototype and static members of this class are inherited by any other class
4316  *
4317  */
4318 (function(flexSetter) {
4319
4320 var Base = Ext.Base = function() {};
4321     Base.prototype = {
4322         $className: 'Ext.Base',
4323
4324         $class: Base,
4325
4326         /**
4327          * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
4328          * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
4329          * for a detailed comparison
4330          *
4331          *     Ext.define('My.Cat', {
4332          *         statics: {
4333          *             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4334          *         },
4335          *
4336          *         constructor: function() {
4337          *             alert(this.self.speciesName); / dependent on 'this'
4338          *
4339          *             return this;
4340          *         },
4341          *
4342          *         clone: function() {
4343          *             return new this.self();
4344          *         }
4345          *     });
4346          *
4347          *
4348          *     Ext.define('My.SnowLeopard', {
4349          *         extend: 'My.Cat',
4350          *         statics: {
4351          *             speciesName: 'Snow Leopard'         // My.SnowLeopard.speciesName = 'Snow Leopard'
4352          *         }
4353          *     });
4354          *
4355          *     var cat = new My.Cat();                     // alerts 'Cat'
4356          *     var snowLeopard = new My.SnowLeopard();     // alerts 'Snow Leopard'
4357          *
4358          *     var clone = snowLeopard.clone();
4359          *     alert(Ext.getClassName(clone));             // alerts 'My.SnowLeopard'
4360          *
4361          * @type Class
4362          * @protected
4363          * @markdown
4364          */
4365         self: Base,
4366
4367         /**
4368          * Default constructor, simply returns `this`
4369          *
4370          * @constructor
4371          * @protected
4372          * @return {Object} this
4373          */
4374         constructor: function() {
4375             return this;
4376         },
4377
4378         /**
4379          * Initialize configuration for this class. a typical example:
4380          *
4381          *     Ext.define('My.awesome.Class', {
4382          *         // The default config
4383          *         config: {
4384          *             name: 'Awesome',
4385          *             isAwesome: true
4386          *         },
4387          *
4388          *         constructor: function(config) {
4389          *             this.initConfig(config);
4390          *
4391          *             return this;
4392          *         }
4393          *     });
4394          *
4395          *     var awesome = new My.awesome.Class({
4396          *         name: 'Super Awesome'
4397          *     });
4398          *
4399          *     alert(awesome.getName()); // 'Super Awesome'
4400          *
4401          * @protected
4402          * @param {Object} config
4403          * @return {Object} mixins The mixin prototypes as key - value pairs
4404          * @markdown
4405          */
4406         initConfig: function(config) {
4407             if (!this.$configInited) {
4408                 this.config = Ext.Object.merge({}, this.config || {}, config || {});
4409
4410                 this.applyConfig(this.config);
4411
4412                 this.$configInited = true;
4413             }
4414
4415             return this;
4416         },
4417
4418         /**
4419          * @private
4420          */
4421         setConfig: function(config) {
4422             this.applyConfig(config || {});
4423
4424             return this;
4425         },
4426
4427         /**
4428          * @private
4429          */
4430         applyConfig: flexSetter(function(name, value) {
4431             var setter = 'set' + Ext.String.capitalize(name);
4432
4433             if (typeof this[setter] === 'function') {
4434                 this[setter].call(this, value);
4435             }
4436
4437             return this;
4438         }),
4439
4440         /**
4441          * Call the parent's overridden method. For example:
4442          *
4443          *     Ext.define('My.own.A', {
4444          *         constructor: function(test) {
4445          *             alert(test);
4446          *         }
4447          *     });
4448          *
4449          *     Ext.define('My.own.B', {
4450          *         extend: 'My.own.A',
4451          *
4452          *         constructor: function(test) {
4453          *             alert(test);
4454          *
4455          *             this.callParent([test + 1]);
4456          *         }
4457          *     });
4458          *
4459          *     Ext.define('My.own.C', {
4460          *         extend: 'My.own.B',
4461          *
4462          *         constructor: function() {
4463          *             alert("Going to call parent's overriden constructor...");
4464          *
4465          *             this.callParent(arguments);
4466          *         }
4467          *     });
4468          *
4469          *     var a = new My.own.A(1); // alerts '1'
4470          *     var b = new My.own.B(1); // alerts '1', then alerts '2'
4471          *     var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
4472          *                              // alerts '2', then alerts '3'
4473          *
4474          * @protected
4475          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4476          * from the current method, for example: `this.callParent(arguments)`
4477          * @return {Mixed} Returns the result from the superclass' method
4478          * @markdown
4479          */
4480         callParent: function(args) {
4481             var method = this.callParent.caller,
4482                 parentClass, methodName;
4483
4484             if (!method.$owner) {
4485                 if (!method.caller) {
4486                     Ext.Error.raise({
4487                         sourceClass: Ext.getClassName(this),
4488                         sourceMethod: "callParent",
4489                         msg: "Attempting to call a protected method from the public scope, which is not allowed"
4490                     });
4491                 }
4492
4493                 method = method.caller;
4494             }
4495
4496             parentClass = method.$owner.superclass;
4497             methodName = method.$name;
4498
4499             if (!(methodName in parentClass)) {
4500                 Ext.Error.raise({
4501                     sourceClass: Ext.getClassName(this),
4502                     sourceMethod: methodName,
4503                     msg: "this.callParent() was called but there's no such method (" + methodName +
4504                          ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"
4505                  });
4506             }
4507
4508             return parentClass[methodName].apply(this, args || []);
4509         },
4510
4511
4512         /**
4513          * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
4514          * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
4515          * `this` points to during run-time
4516          *
4517          *     Ext.define('My.Cat', {
4518          *         statics: {
4519          *             totalCreated: 0,
4520          *             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4521          *         },
4522          *  
4523          *         constructor: function() {
4524          *             var statics = this.statics();
4525          *  
4526          *             alert(statics.speciesName);     // always equals to 'Cat' no matter what 'this' refers to
4527          *                                             // equivalent to: My.Cat.speciesName
4528          *  
4529          *             alert(this.self.speciesName);   // dependent on 'this'
4530          *  
4531          *             statics.totalCreated++;
4532          *  
4533          *             return this;
4534          *         },
4535          *  
4536          *         clone: function() {
4537          *             var cloned = new this.self;                      // dependent on 'this'
4538          *  
4539          *             cloned.groupName = this.statics().speciesName;   // equivalent to: My.Cat.speciesName
4540          *  
4541          *             return cloned;
4542          *         }
4543          *     });
4544          *
4545          *
4546          *     Ext.define('My.SnowLeopard', {
4547          *         extend: 'My.Cat',
4548          *  
4549          *         statics: {
4550          *             speciesName: 'Snow Leopard'     // My.SnowLeopard.speciesName = 'Snow Leopard'
4551          *         },
4552          *  
4553          *         constructor: function() {
4554          *             this.callParent();
4555          *         }
4556          *     });
4557          *
4558          *     var cat = new My.Cat();                 // alerts 'Cat', then alerts 'Cat'
4559          *
4560          *     var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
4561          *
4562          *     var clone = snowLeopard.clone();
4563          *     alert(Ext.getClassName(clone));         // alerts 'My.SnowLeopard'
4564          *     alert(clone.groupName);                 // alerts 'Cat'
4565          *
4566          *     alert(My.Cat.totalCreated);             // alerts 3
4567          *
4568          * @protected
4569          * @return {Class}
4570          * @markdown
4571          */
4572         statics: function() {
4573             var method = this.statics.caller,
4574                 self = this.self;
4575
4576             if (!method) {
4577                 return self;
4578             }
4579
4580             return method.$owner;
4581         },
4582
4583         /**
4584          * Call the original method that was previously overridden with {@link Ext.Base#override}
4585          *
4586          *     Ext.define('My.Cat', {
4587          *         constructor: function() {
4588          *             alert("I'm a cat!");
4589          *   
4590          *             return this;
4591          *         }
4592          *     });
4593          *
4594          *     My.Cat.override({
4595          *         constructor: function() {
4596          *             alert("I'm going to be a cat!");
4597          *   
4598          *             var instance = this.callOverridden();
4599          *   
4600          *             alert("Meeeeoooowwww");
4601          *   
4602          *             return instance;
4603          *         }
4604          *     });
4605          *
4606          *     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4607          *                               // alerts "I'm a cat!"
4608          *                               // alerts "Meeeeoooowwww"
4609          *
4610          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4611          * @return {Mixed} Returns the result after calling the overridden method
4612          * @markdown
4613          */
4614         callOverridden: function(args) {
4615             var method = this.callOverridden.caller;
4616
4617             if (!method.$owner) {
4618                 Ext.Error.raise({
4619                     sourceClass: Ext.getClassName(this),
4620                     sourceMethod: "callOverridden",
4621                     msg: "Attempting to call a protected method from the public scope, which is not allowed"
4622                 });
4623             }
4624
4625             if (!method.$previous) {
4626                 Ext.Error.raise({
4627                     sourceClass: Ext.getClassName(this),
4628                     sourceMethod: "callOverridden",
4629                     msg: "this.callOverridden was called in '" + method.$name +
4630                          "' but this method has never been overridden"
4631                  });
4632             }
4633
4634             return method.$previous.apply(this, args || []);
4635         },
4636
4637         destroy: function() {}
4638     };
4639
4640     // These static properties will be copied to every newly created class with {@link Ext#define}
4641     Ext.apply(Ext.Base, {
4642         /**
4643          * Create a new instance of this Class.
4644          *
4645          *     Ext.define('My.cool.Class', {
4646          *         ...
4647          *     });
4648          *      
4649          *     My.cool.Class.create({
4650          *         someConfig: true
4651          *     });
4652          *
4653          * @property create
4654          * @static
4655          * @type Function
4656          * @markdown
4657          */
4658         create: function() {
4659             return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
4660         },
4661
4662         /**
4663          * @private
4664          */
4665         own: flexSetter(function(name, value) {
4666             if (typeof value === 'function') {
4667                 this.ownMethod(name, value);
4668             }
4669             else {
4670                 this.prototype[name] = value;
4671             }
4672         }),
4673
4674         /**
4675          * @private
4676          */
4677         ownMethod: function(name, fn) {
4678             var originalFn;
4679
4680             if (fn.$owner !== undefined && fn !== Ext.emptyFn) {
4681                 originalFn = fn;
4682
4683                 fn = function() {
4684                     return originalFn.apply(this, arguments);
4685                 };
4686             }
4687
4688             var className;
4689             className = Ext.getClassName(this);
4690             if (className) {
4691                 fn.displayName = className + '#' + name;
4692             }
4693             fn.$owner = this;
4694             fn.$name = name;
4695
4696             this.prototype[name] = fn;
4697         },
4698
4699         /**
4700          * Add / override static properties of this class.
4701          *
4702          *     Ext.define('My.cool.Class', {
4703          *         ...
4704          *     });
4705          *
4706          *     My.cool.Class.addStatics({
4707          *         someProperty: 'someValue',      // My.cool.Class.someProperty = 'someValue'
4708          *         method1: function() { ... },    // My.cool.Class.method1 = function() { ... };
4709          *         method2: function() { ... }     // My.cool.Class.method2 = function() { ... };
4710          *     });
4711          *
4712          * @property addStatics
4713          * @static
4714          * @type Function
4715          * @param {Object} members
4716          * @markdown
4717          */
4718         addStatics: function(members) {
4719             for (var name in members) {
4720                 if (members.hasOwnProperty(name)) {
4721                     this[name] = members[name];
4722                 }
4723             }
4724
4725             return this;
4726         },
4727
4728         /**
4729          * Add methods / properties to the prototype of this class.
4730          *
4731          *     Ext.define('My.awesome.Cat', {
4732          *         constructor: function() {
4733          *             ...
4734          *         }
4735          *     });
4736          *
4737          *      My.awesome.Cat.implement({
4738          *          meow: function() {
4739          *             alert('Meowww...');
4740          *          }
4741          *      });
4742          *
4743          *      var kitty = new My.awesome.Cat;
4744          *      kitty.meow();
4745          *
4746          * @property implement
4747          * @static
4748          * @type Function
4749          * @param {Object} members
4750          * @markdown
4751          */
4752         implement: function(members) {
4753             var prototype = this.prototype,
4754                 name, i, member, previous;
4755             var className = Ext.getClassName(this);
4756             for (name in members) {
4757                 if (members.hasOwnProperty(name)) {
4758                     member = members[name];
4759
4760                     if (typeof member === 'function') {
4761                         member.$owner = this;
4762                         member.$name = name;
4763                         if (className) {
4764                             member.displayName = className + '#' + name;
4765                         }
4766                     }
4767
4768                     prototype[name] = member;
4769                 }
4770             }
4771
4772             if (Ext.enumerables) {
4773                 var enumerables = Ext.enumerables;
4774
4775                 for (i = enumerables.length; i--;) {
4776                     name = enumerables[i];
4777
4778                     if (members.hasOwnProperty(name)) {
4779                         member = members[name];
4780                         member.$owner = this;
4781                         member.$name = name;
4782                         prototype[name] = member;
4783                     }
4784                 }
4785             }
4786         },
4787
4788         /**
4789          * Borrow another class' members to the prototype of this class.
4790          *
4791          *     Ext.define('Bank', {
4792          *         money: '$$$',
4793          *         printMoney: function() {
4794          *             alert('$$$$$$$');
4795          *         }
4796          *     });
4797          *
4798          *     Ext.define('Thief', {
4799          *         ...
4800          *     });
4801          *
4802          *     Thief.borrow(Bank, ['money', 'printMoney']);
4803          *
4804          *     var steve = new Thief();
4805          *
4806          *     alert(steve.money); // alerts '$$$'
4807          *     steve.printMoney(); // alerts '$$$$$$$'
4808          *
4809          * @property borrow
4810          * @static
4811          * @type Function
4812          * @param {Ext.Base} fromClass The class to borrow members from
4813          * @param {Array/String} members The names of the members to borrow
4814          * @return {Ext.Base} this
4815          * @markdown
4816          */
4817         borrow: function(fromClass, members) {
4818             var fromPrototype = fromClass.prototype,
4819                 i, ln, member;
4820
4821             members = Ext.Array.from(members);
4822
4823             for (i = 0, ln = members.length; i < ln; i++) {
4824                 member = members[i];
4825
4826                 this.own(member, fromPrototype[member]);
4827             }
4828
4829             return this;
4830         },
4831
4832         /**
4833          * Override prototype members of this class. Overridden methods can be invoked via
4834          * {@link Ext.Base#callOverridden}
4835          *
4836          *     Ext.define('My.Cat', {
4837          *         constructor: function() {
4838          *             alert("I'm a cat!");
4839          *
4840          *             return this;
4841          *         }
4842          *     });
4843          *
4844          *     My.Cat.override({
4845          *         constructor: function() {
4846          *             alert("I'm going to be a cat!");
4847          *
4848          *             var instance = this.callOverridden();
4849          *
4850          *             alert("Meeeeoooowwww");
4851          *
4852          *             return instance;
4853          *         }
4854          *     });
4855          *
4856          *     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4857          *                               // alerts "I'm a cat!"
4858          *                               // alerts "Meeeeoooowwww"
4859          *
4860          * @property override
4861          * @static
4862          * @type Function
4863          * @param {Object} members
4864          * @return {Ext.Base} this
4865          * @markdown
4866          */
4867         override: function(members) {
4868             var prototype = this.prototype,
4869                 name, i, member, previous;
4870
4871             for (name in members) {
4872                 if (members.hasOwnProperty(name)) {
4873                     member = members[name];
4874
4875                     if (typeof member === 'function') {
4876                         if (typeof prototype[name] === 'function') {
4877                             previous = prototype[name];
4878                             member.$previous = previous;
4879                         }
4880
4881                         this.ownMethod(name, member);
4882                     }
4883                     else {
4884                         prototype[name] = member;
4885                     }
4886                 }
4887             }
4888
4889             if (Ext.enumerables) {
4890                 var enumerables = Ext.enumerables;
4891
4892                 for (i = enumerables.length; i--;) {
4893                     name = enumerables[i];
4894
4895                     if (members.hasOwnProperty(name)) {
4896                         if (prototype[name] !== undefined) {
4897                             previous = prototype[name];
4898                             members[name].$previous = previous;
4899                         }
4900
4901                         this.ownMethod(name, members[name]);
4902                     }
4903                 }
4904             }
4905
4906             return this;
4907         },
4908
4909         /**
4910          * Used internally by the mixins pre-processor
4911          * @private
4912          */
4913         mixin: flexSetter(function(name, cls) {
4914             var mixin = cls.prototype,
4915                 my = this.prototype,
4916                 i, fn;
4917
4918             for (i in mixin) {
4919                 if (mixin.hasOwnProperty(i)) {
4920                     if (my[i] === undefined) {
4921                         if (typeof mixin[i] === 'function') {
4922                             fn = mixin[i];
4923
4924                             if (fn.$owner === undefined) {
4925                                 this.ownMethod(i, fn);
4926                             }
4927                             else {
4928                                 my[i] = fn;
4929                             }
4930                         }
4931                         else {
4932                             my[i] = mixin[i];
4933                         }
4934                     }
4935                     else if (i === 'config' && my.config && mixin.config) {
4936                         Ext.Object.merge(my.config, mixin.config);
4937                     }
4938                 }
4939             }
4940
4941             if (my.mixins === undefined) {
4942                 my.mixins = {};
4943             }
4944
4945             my.mixins[name] = mixin;
4946         }),
4947
4948         /**
4949          * Get the current class' name in string format.
4950          *
4951          *     Ext.define('My.cool.Class', {
4952          *         constructor: function() {
4953          *             alert(this.self.getName()); // alerts 'My.cool.Class'
4954          *         }
4955          *     });
4956          *
4957          *     My.cool.Class.getName(); // 'My.cool.Class'
4958          *
4959          * @return {String} className
4960          * @markdown
4961          */
4962         getName: function() {
4963             return Ext.getClassName(this);
4964         },
4965
4966         /**
4967          * Create aliases for existing prototype methods. Example:
4968          *
4969          *     Ext.define('My.cool.Class', {
4970          *         method1: function() { ... },
4971          *         method2: function() { ... }
4972          *     });
4973          *
4974          *     var test = new My.cool.Class();
4975          *
4976          *     My.cool.Class.createAlias({
4977          *         method3: 'method1',
4978          *         method4: 'method2'
4979          *     });
4980          *
4981          *     test.method3(); // test.method1()
4982          *
4983          *     My.cool.Class.createAlias('method5', 'method3');
4984          *
4985          *     test.method5(); // test.method3() -> test.method1()
4986          *
4987          * @property createAlias
4988          * @static
4989          * @type Function
4990          * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
4991          * {@link Ext.Function#flexSetter flexSetter}
4992          * @param {String/Object} origin The original method name
4993          * @markdown
4994          */
4995         createAlias: flexSetter(function(alias, origin) {
4996             this.prototype[alias] = this.prototype[origin];
4997         })
4998     });
4999
5000 })(Ext.Function.flexSetter);
5001
5002 /**
5003  * @author Jacky Nguyen <jacky@sencha.com>
5004  * @docauthor Jacky Nguyen <jacky@sencha.com>
5005  * @class Ext.Class
5006  * 
5007  * Handles class creation throughout the whole framework. Note that most of the time {@link Ext#define Ext.define} should
5008  * be used instead, since it's a higher level wrapper that aliases to {@link Ext.ClassManager#create}
5009  * to enable namespacing and dynamic dependency resolution.
5010  * 
5011  * # Basic syntax: #
5012  * 
5013  *     Ext.define(className, properties);
5014  * 
5015  * in which `properties` is an object represent a collection of properties that apply to the class. See
5016  * {@link Ext.ClassManager#create} for more detailed instructions.
5017  * 
5018  *     Ext.define('Person', {
5019  *          name: 'Unknown',
5020  * 
5021  *          constructor: function(name) {
5022  *              if (name) {
5023  *                  this.name = name;
5024  *              }
5025  * 
5026  *              return this;
5027  *          },
5028  * 
5029  *          eat: function(foodType) {
5030  *              alert("I'm eating: " + foodType);
5031  * 
5032  *              return this;
5033  *          }
5034  *     });
5035  * 
5036  *     var aaron = new Person("Aaron");
5037  *     aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
5038  * 
5039  * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
5040  * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
5041  * 
5042  * # Inheritance: #
5043  * 
5044  *     Ext.define('Developer', {
5045  *          extend: 'Person',
5046  * 
5047  *          constructor: function(name, isGeek) {
5048  *              this.isGeek = isGeek;
5049  * 
5050  *              // Apply a method from the parent class' prototype
5051  *              this.callParent([name]);
5052  * 
5053  *              return this;
5054  * 
5055  *          },
5056  * 
5057  *          code: function(language) {
5058  *              alert("I'm coding in: " + language);
5059  * 
5060  *              this.eat("Bugs");
5061  * 
5062  *              return this;
5063  *          }
5064  *     });
5065  * 
5066  *     var jacky = new Developer("Jacky", true);
5067  *     jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
5068  *                               // alert("I'm eating: Bugs");
5069  * 
5070  * See {@link Ext.Base#callParent} for more details on calling superclass' methods
5071  * 
5072  * # Mixins: #
5073  * 
5074  *     Ext.define('CanPlayGuitar', {
5075  *          playGuitar: function() {
5076  *             alert("F#...G...D...A");
5077  *          }
5078  *     });
5079  * 
5080  *     Ext.define('CanComposeSongs', {
5081  *          composeSongs: function() { ... }
5082  *     });
5083  * 
5084  *     Ext.define('CanSing', {
5085  *          sing: function() {
5086  *              alert("I'm on the highway to hell...")
5087  *          }
5088  *     });
5089  * 
5090  *     Ext.define('Musician', {
5091  *          extend: 'Person',
5092  * 
5093  *          mixins: {
5094  *              canPlayGuitar: 'CanPlayGuitar',
5095  *              canComposeSongs: 'CanComposeSongs',
5096  *              canSing: 'CanSing'
5097  *          }
5098  *     })
5099  * 
5100  *     Ext.define('CoolPerson', {
5101  *          extend: 'Person',
5102  * 
5103  *          mixins: {
5104  *              canPlayGuitar: 'CanPlayGuitar',
5105  *              canSing: 'CanSing'
5106  *          },
5107  * 
5108  *          sing: function() {
5109  *              alert("Ahem....");
5110  * 
5111  *              this.mixins.canSing.sing.call(this);
5112  * 
5113  *              alert("[Playing guitar at the same time...]");
5114  * 
5115  *              this.playGuitar();
5116  *          }
5117  *     });
5118  * 
5119  *     var me = new CoolPerson("Jacky");
5120  * 
5121  *     me.sing(); // alert("Ahem...");
5122  *                // alert("I'm on the highway to hell...");
5123  *                // alert("[Playing guitar at the same time...]");
5124  *                // alert("F#...G...D...A");
5125  * 
5126  * # Config: #
5127  * 
5128  *     Ext.define('SmartPhone', {
5129  *          config: {
5130  *              hasTouchScreen: false,
5131  *              operatingSystem: 'Other',
5132  *              price: 500
5133  *          },
5134  * 
5135  *          isExpensive: false,
5136  * 
5137  *          constructor: function(config) {
5138  *              this.initConfig(config);
5139  * 
5140  *              return this;
5141  *          },
5142  * 
5143  *          applyPrice: function(price) {
5144  *              this.isExpensive = (price > 500);
5145  * 
5146  *              return price;
5147  *          },
5148  * 
5149  *          applyOperatingSystem: function(operatingSystem) {
5150  *              if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
5151  *                  return 'Other';
5152  *              }
5153  * 
5154  *              return operatingSystem;
5155  *          }
5156  *     });
5157  * 
5158  *     var iPhone = new SmartPhone({
5159  *          hasTouchScreen: true,
5160  *          operatingSystem: 'iOS'
5161  *     });
5162  * 
5163  *     iPhone.getPrice(); // 500;
5164  *     iPhone.getOperatingSystem(); // 'iOS'
5165  *     iPhone.getHasTouchScreen(); // true;
5166  *     iPhone.hasTouchScreen(); // true
5167  * 
5168  *     iPhone.isExpensive; // false;
5169  *     iPhone.setPrice(600);
5170  *     iPhone.getPrice(); // 600
5171  *     iPhone.isExpensive; // true;
5172  * 
5173  *     iPhone.setOperatingSystem('AlienOS');
5174  *     iPhone.getOperatingSystem(); // 'Other'
5175  * 
5176  * # Statics: #
5177  * 
5178  *     Ext.define('Computer', {
5179  *          statics: {
5180  *              factory: function(brand) {
5181  *                 // 'this' in static methods refer to the class itself
5182  *                  return new this(brand);
5183  *              }
5184  *          },
5185  * 
5186  *          constructor: function() { ... }
5187  *     });
5188  * 
5189  *     var dellComputer = Computer.factory('Dell');
5190  * 
5191  * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
5192  * static properties within class methods
5193  *
5194  */
5195 (function() {
5196
5197     var Class,
5198         Base = Ext.Base,
5199         baseStaticProperties = [],
5200         baseStaticProperty;
5201
5202     for (baseStaticProperty in Base) {
5203         if (Base.hasOwnProperty(baseStaticProperty)) {
5204             baseStaticProperties.push(baseStaticProperty);
5205         }
5206     }
5207
5208     /**
5209      * @constructor
5210      * @param {Object} classData An object represent the properties of this class
5211      * @param {Function} createdFn Optional, the callback function to be executed when this class is fully created.
5212      * Note that the creation process can be asynchronous depending on the pre-processors used.
5213      * @return {Ext.Base} The newly created class
5214      */
5215     Ext.Class = Class = function(newClass, classData, onClassCreated) {
5216         if (typeof newClass !== 'function') {
5217             onClassCreated = classData;
5218             classData = newClass;
5219             newClass = function() {
5220                 return this.constructor.apply(this, arguments);
5221             };
5222         }
5223
5224         if (!classData) {
5225             classData = {};
5226         }
5227
5228         var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
5229             registeredPreprocessors = Class.getPreprocessors(),
5230             index = 0,
5231             preprocessors = [],
5232             preprocessor, preprocessors, staticPropertyName, process, i, j, ln;
5233
5234         for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
5235             staticPropertyName = baseStaticProperties[i];
5236             newClass[staticPropertyName] = Base[staticPropertyName];
5237         }
5238
5239         delete classData.preprocessors;
5240
5241         for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
5242             preprocessor = preprocessorStack[j];
5243
5244             if (typeof preprocessor === 'string') {
5245                 preprocessor = registeredPreprocessors[preprocessor];
5246
5247                 if (!preprocessor.always) {
5248                     if (classData.hasOwnProperty(preprocessor.name)) {
5249                         preprocessors.push(preprocessor.fn);
5250                     }
5251                 }
5252                 else {
5253                     preprocessors.push(preprocessor.fn);
5254                 }
5255             }
5256             else {
5257                 preprocessors.push(preprocessor);
5258             }
5259         }
5260
5261         classData.onClassCreated = onClassCreated;
5262
5263         classData.onBeforeClassCreated = function(cls, data) {
5264             onClassCreated = data.onClassCreated;
5265
5266             delete data.onBeforeClassCreated;
5267             delete data.onClassCreated;
5268
5269             cls.implement(data);
5270
5271             if (onClassCreated) {
5272                 onClassCreated.call(cls, cls);
5273             }
5274         };
5275
5276         process = function(cls, data) {
5277             preprocessor = preprocessors[index++];
5278
5279             if (!preprocessor) {
5280                 data.onBeforeClassCreated.apply(this, arguments);
5281                 return;
5282             }
5283
5284             if (preprocessor.call(this, cls, data, process) !== false) {
5285                 process.apply(this, arguments);
5286             }
5287         };
5288
5289         process.call(Class, newClass, classData);
5290
5291         return newClass;
5292     };
5293
5294     Ext.apply(Class, {
5295
5296         /** @private */
5297         preprocessors: {},
5298
5299         /**
5300          * Register a new pre-processor to be used during the class creation process
5301          *
5302          * @member Ext.Class registerPreprocessor
5303          * @param {String} name The pre-processor's name
5304          * @param {Function} fn The callback function to be executed. Typical format:
5305
5306     function(cls, data, fn) {
5307         // Your code here
5308
5309         // Execute this when the processing is finished.
5310         // Asynchronous processing is perfectly ok
5311         if (fn) {
5312             fn.call(this, cls, data);
5313         }
5314     });
5315
5316          * Passed arguments for this function are:
5317          *
5318          * - `{Function} cls`: The created class
5319          * - `{Object} data`: The set of properties passed in {@link Ext.Class} constructor
5320          * - `{Function} fn`: The callback function that <b>must</b> to be executed when this pre-processor finishes,
5321          * regardless of whether the processing is synchronous or aynchronous
5322          *
5323          * @return {Ext.Class} this
5324          * @markdown
5325          */
5326         registerPreprocessor: function(name, fn, always) {
5327             this.preprocessors[name] = {
5328                 name: name,
5329                 always: always ||  false,
5330                 fn: fn
5331             };
5332
5333             return this;
5334         },
5335
5336         /**
5337          * Retrieve a pre-processor callback function by its name, which has been registered before
5338          *
5339          * @param {String} name
5340          * @return {Function} preprocessor
5341          */
5342         getPreprocessor: function(name) {
5343             return this.preprocessors[name];
5344         },
5345
5346         getPreprocessors: function() {
5347             return this.preprocessors;
5348         },
5349
5350         /**
5351          * Retrieve the array stack of default pre-processors
5352          *
5353          * @return {Function} defaultPreprocessors
5354          */
5355         getDefaultPreprocessors: function() {
5356             return this.defaultPreprocessors || [];
5357         },
5358
5359         /**
5360          * Set the default array stack of default pre-processors
5361          *
5362          * @param {Array} preprocessors
5363          * @return {Ext.Class} this
5364          */
5365         setDefaultPreprocessors: function(preprocessors) {
5366             this.defaultPreprocessors = Ext.Array.from(preprocessors);
5367
5368             return this;
5369         },
5370
5371         /**
5372          * Insert this pre-processor at a specific position in the stack, optionally relative to
5373          * any existing pre-processor. For example:
5374
5375     Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
5376         // Your code here
5377
5378         if (fn) {
5379             fn.call(this, cls, data);
5380         }
5381     }).insertDefaultPreprocessor('debug', 'last');
5382
5383          * @param {String} name The pre-processor name. Note that it needs to be registered with
5384          * {@link Ext#registerPreprocessor registerPreprocessor} before this
5385          * @param {String} offset The insertion position. Four possible values are:
5386          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
5387          * @param {String} relativeName
5388          * @return {Ext.Class} this
5389          * @markdown
5390          */
5391         setDefaultPreprocessorPosition: function(name, offset, relativeName) {
5392             var defaultPreprocessors = this.defaultPreprocessors,
5393                 index;
5394
5395             if (typeof offset === 'string') {
5396                 if (offset === 'first') {
5397                     defaultPreprocessors.unshift(name);
5398
5399                     return this;
5400                 }
5401                 else if (offset === 'last') {
5402                     defaultPreprocessors.push(name);
5403
5404                     return this;
5405                 }
5406
5407                 offset = (offset === 'after') ? 1 : -1;
5408             }
5409
5410             index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
5411
5412             if (index !== -1) {
5413                 defaultPreprocessors.splice(Math.max(0, index + offset), 0, name);
5414             }
5415
5416             return this;
5417         }
5418     });
5419
5420     Class.registerPreprocessor('extend', function(cls, data) {
5421         var extend = data.extend,
5422             base = Ext.Base,
5423             basePrototype = base.prototype,
5424             prototype = function() {},
5425             parent, i, k, ln, staticName, parentStatics,
5426             parentPrototype, clsPrototype;
5427
5428         if (extend && extend !== Object) {
5429             parent = extend;
5430         }
5431         else {
5432             parent = base;
5433         }
5434
5435         parentPrototype = parent.prototype;
5436
5437         prototype.prototype = parentPrototype;
5438         clsPrototype = cls.prototype = new prototype();
5439
5440         if (!('$class' in parent)) {
5441             for (i in basePrototype) {
5442                 if (!parentPrototype[i]) {
5443                     parentPrototype[i] = basePrototype[i];
5444                 }
5445             }
5446         }
5447
5448         clsPrototype.self = cls;
5449
5450         cls.superclass = clsPrototype.superclass = parentPrototype;
5451
5452         delete data.extend;
5453
5454         // Statics inheritance
5455         parentStatics = parentPrototype.$inheritableStatics;
5456
5457         if (parentStatics) {
5458             for (k = 0, ln = parentStatics.length; k < ln; k++) {
5459                 staticName = parentStatics[k];
5460
5461                 if (!cls.hasOwnProperty(staticName)) {
5462                     cls[staticName] = parent[staticName];
5463                 }
5464             }
5465         }
5466
5467         // Merge the parent class' config object without referencing it
5468         if (parentPrototype.config) {
5469             clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
5470         }
5471         else {
5472             clsPrototype.config = {};
5473         }
5474
5475         if (clsPrototype.$onExtended) {
5476             clsPrototype.$onExtended.call(cls, cls, data);
5477         }
5478
5479         if (data.onClassExtended) {
5480             clsPrototype.$onExtended = data.onClassExtended;
5481             delete data.onClassExtended;
5482         }
5483
5484     }, true);
5485
5486     Class.registerPreprocessor('statics', function(cls, data) {
5487         var statics = data.statics,
5488             name;
5489
5490         for (name in statics) {
5491             if (statics.hasOwnProperty(name)) {
5492                 cls[name] = statics[name];
5493             }
5494         }
5495
5496         delete data.statics;
5497     });
5498
5499     Class.registerPreprocessor('inheritableStatics', function(cls, data) {
5500         var statics = data.inheritableStatics,
5501             inheritableStatics,
5502             prototype = cls.prototype,
5503             name;
5504
5505         inheritableStatics = prototype.$inheritableStatics;
5506
5507         if (!inheritableStatics) {
5508             inheritableStatics = prototype.$inheritableStatics = [];
5509         }
5510
5511         for (name in statics) {
5512             if (statics.hasOwnProperty(name)) {
5513                 cls[name] = statics[name];
5514                 inheritableStatics.push(name);
5515             }
5516         }
5517
5518         delete data.inheritableStatics;
5519     });
5520
5521     Class.registerPreprocessor('mixins', function(cls, data) {
5522         cls.mixin(data.mixins);
5523
5524         delete data.mixins;
5525     });
5526
5527     Class.registerPreprocessor('config', function(cls, data) {
5528         var prototype = cls.prototype;
5529
5530         Ext.Object.each(data.config, function(name) {
5531             var cName = name.charAt(0).toUpperCase() + name.substr(1),
5532                 pName = name,
5533                 apply = 'apply' + cName,
5534                 setter = 'set' + cName,
5535                 getter = 'get' + cName;
5536
5537             if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
5538                 data[apply] = function(val) {
5539                     return val;
5540                 };
5541             }
5542
5543             if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
5544                 data[setter] = function(val) {
5545                     var ret = this[apply].call(this, val, this[pName]);
5546
5547                     if (ret !== undefined) {
5548                         this[pName] = ret;
5549                     }
5550
5551                     return this;
5552                 };
5553             }
5554
5555             if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
5556                 data[getter] = function() {
5557                     return this[pName];
5558                 };
5559             }
5560         });
5561
5562         Ext.Object.merge(prototype.config, data.config);
5563         delete data.config;
5564     });
5565
5566     Class.setDefaultPreprocessors(['extend', 'statics', 'inheritableStatics', 'mixins', 'config']);
5567
5568     // Backwards compatible
5569     Ext.extend = function(subclass, superclass, members) {
5570         if (arguments.length === 2 && Ext.isObject(superclass)) {
5571             members = superclass;
5572             superclass = subclass;
5573             subclass = null;
5574         }
5575
5576         var cls;
5577
5578         if (!superclass) {
5579             Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
5580         }
5581
5582         members.extend = superclass;
5583         members.preprocessors = ['extend', 'mixins', 'config', 'statics'];
5584
5585         if (subclass) {
5586             cls = new Class(subclass, members);
5587         }
5588         else {
5589             cls = new Class(members);
5590         }
5591
5592         cls.prototype.override = function(o) {
5593             for (var m in o) {
5594                 if (o.hasOwnProperty(m)) {
5595                     this[m] = o[m];
5596                 }
5597             }
5598         };
5599
5600         return cls;
5601     };
5602
5603 })();
5604
5605 /**
5606  * @author Jacky Nguyen <jacky@sencha.com>
5607  * @docauthor Jacky Nguyen <jacky@sencha.com>
5608  * @class Ext.ClassManager
5609
5610 Ext.ClassManager manages all classes and handles mapping from string class name to
5611 actual class objects throughout the whole framework. It is not generally accessed directly, rather through
5612 these convenient shorthands:
5613
5614 - {@link Ext#define Ext.define}
5615 - {@link Ext#create Ext.create}
5616 - {@link Ext#widget Ext.widget}
5617 - {@link Ext#getClass Ext.getClass}
5618 - {@link Ext#getClassName Ext.getClassName}
5619
5620  * @singleton
5621  * @markdown
5622  */
5623 (function(Class, alias) {
5624
5625     var slice = Array.prototype.slice;
5626
5627     var Manager = Ext.ClassManager = {
5628
5629         /**
5630          * @property classes
5631          * @type Object
5632          * All classes which were defined through the ClassManager. Keys are the
5633          * name of the classes and the values are references to the classes.
5634          * @private
5635          */
5636         classes: {},
5637
5638         /**
5639          * @private
5640          */
5641         existCache: {},
5642
5643         /**
5644          * @private
5645          */
5646         namespaceRewrites: [{
5647             from: 'Ext.',
5648             to: Ext
5649         }],
5650
5651         /**
5652          * @private
5653          */
5654         maps: {
5655             alternateToName: {},
5656             aliasToName: {},
5657             nameToAliases: {}
5658         },
5659
5660         /** @private */
5661         enableNamespaceParseCache: true,
5662
5663         /** @private */
5664         namespaceParseCache: {},
5665
5666         /** @private */
5667         instantiators: [],
5668
5669         /** @private */
5670         instantiationCounts: {},
5671
5672         /**
5673          * Checks if a class has already been created.
5674          *
5675          * @param {String} className
5676          * @return {Boolean} exist
5677          */
5678         isCreated: function(className) {
5679             var i, ln, part, root, parts;
5680
5681             if (typeof className !== 'string' || className.length < 1) {
5682                 Ext.Error.raise({
5683                     sourceClass: "Ext.ClassManager",
5684                     sourceMethod: "exist",
5685                     msg: "Invalid classname, must be a string and must not be empty"
5686                 });
5687             }
5688
5689             if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
5690                 return true;
5691             }
5692
5693             root = Ext.global;
5694             parts = this.parseNamespace(className);
5695
5696             for (i = 0, ln = parts.length; i < ln; i++) {
5697                 part = parts[i];
5698
5699                 if (typeof part !== 'string') {
5700                     root = part;
5701                 } else {
5702                     if (!root || !root[part]) {
5703                         return false;
5704                     }
5705
5706                     root = root[part];
5707                 }
5708             }
5709
5710             Ext.Loader.historyPush(className);
5711
5712             this.existCache[className] = true;
5713
5714             return true;
5715         },
5716
5717         /**
5718          * Supports namespace rewriting
5719          * @private
5720          */
5721         parseNamespace: function(namespace) {
5722             if (typeof namespace !== 'string') {
5723                 Ext.Error.raise({
5724                     sourceClass: "Ext.ClassManager",
5725                     sourceMethod: "parseNamespace",
5726                     msg: "Invalid namespace, must be a string"
5727                 });
5728             }
5729
5730             var cache = this.namespaceParseCache;
5731
5732             if (this.enableNamespaceParseCache) {
5733                 if (cache.hasOwnProperty(namespace)) {
5734                     return cache[namespace];
5735                 }
5736             }
5737
5738             var parts = [],
5739                 rewrites = this.namespaceRewrites,
5740                 rewrite, from, to, i, ln, root = Ext.global;
5741
5742             for (i = 0, ln = rewrites.length; i < ln; i++) {
5743                 rewrite = rewrites[i];
5744                 from = rewrite.from;
5745                 to = rewrite.to;
5746
5747                 if (namespace === from || namespace.substring(0, from.length) === from) {
5748                     namespace = namespace.substring(from.length);
5749
5750                     if (typeof to !== 'string') {
5751                         root = to;
5752                     } else {
5753                         parts = parts.concat(to.split('.'));
5754                     }
5755
5756                     break;
5757                 }
5758             }
5759
5760             parts.push(root);
5761
5762             parts = parts.concat(namespace.split('.'));
5763
5764             if (this.enableNamespaceParseCache) {
5765                 cache[namespace] = parts;
5766             }
5767
5768             return parts;
5769         },
5770
5771         /**
5772          * Creates a namespace and assign the `value` to the created object
5773
5774     Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
5775
5776     alert(MyCompany.pkg.Example === someObject); // alerts true
5777
5778          * @param {String} name
5779          * @param {Mixed} value
5780          * @markdown
5781          */
5782         setNamespace: function(name, value) {
5783             var root = Ext.global,
5784                 parts = this.parseNamespace(name),
5785                 leaf = parts.pop(),
5786                 i, ln, part;
5787
5788             for (i = 0, ln = parts.length; i < ln; i++) {
5789                 part = parts[i];
5790
5791                 if (typeof part !== 'string') {
5792                     root = part;
5793                 } else {
5794                     if (!root[part]) {
5795                         root[part] = {};
5796                     }
5797
5798                     root = root[part];
5799                 }
5800             }
5801
5802             root[leaf] = value;
5803
5804             return root[leaf];
5805         },
5806
5807         /**
5808          * The new Ext.ns, supports namespace rewriting
5809          * @private
5810          */
5811         createNamespaces: function() {
5812             var root = Ext.global,
5813                 parts, part, i, j, ln, subLn;
5814
5815             for (i = 0, ln = arguments.length; i < ln; i++) {
5816                 parts = this.parseNamespace(arguments[i]);
5817
5818                 for (j = 0, subLn = parts.length; j < subLn; j++) {
5819                     part = parts[j];
5820
5821                     if (typeof part !== 'string') {
5822                         root = part;
5823                     } else {
5824                         if (!root[part]) {
5825                             root[part] = {};
5826                         }
5827
5828                         root = root[part];
5829                     }
5830                 }
5831             }
5832
5833             return root;
5834         },
5835
5836         /**
5837          * Sets a name reference to a class.
5838          *
5839          * @param {String} name
5840          * @param {Object} value
5841          * @return {Ext.ClassManager} this
5842          */
5843         set: function(name, value) {
5844             var targetName = this.getName(value);
5845
5846             this.classes[name] = this.setNamespace(name, value);
5847
5848             if (targetName && targetName !== name) {
5849                 this.maps.alternateToName[name] = targetName;
5850             }
5851
5852             return this;
5853         },
5854
5855         /**
5856          * Retrieve a class by its name.
5857          *
5858          * @param {String} name
5859          * @return {Class} class
5860          */
5861         get: function(name) {
5862             if (this.classes.hasOwnProperty(name)) {
5863                 return this.classes[name];
5864             }
5865
5866             var root = Ext.global,
5867                 parts = this.parseNamespace(name),
5868                 part, i, ln;
5869
5870             for (i = 0, ln = parts.length; i < ln; i++) {
5871                 part = parts[i];
5872
5873                 if (typeof part !== 'string') {
5874                     root = part;
5875                 } else {
5876                     if (!root || !root[part]) {
5877                         return null;
5878                     }
5879
5880                     root = root[part];
5881                 }
5882             }
5883
5884             return root;
5885         },
5886
5887         /**
5888          * Register the alias for a class.
5889          *
5890          * @param {Class/String} cls a reference to a class or a className
5891          * @param {String} alias Alias to use when referring to this class
5892          */
5893         setAlias: function(cls, alias) {
5894             var aliasToNameMap = this.maps.aliasToName,
5895                 nameToAliasesMap = this.maps.nameToAliases,
5896                 className;
5897
5898             if (typeof cls === 'string') {
5899                 className = cls;
5900             } else {
5901                 className = this.getName(cls);
5902             }
5903
5904             if (alias && aliasToNameMap[alias] !== className) {
5905                 if (aliasToNameMap.hasOwnProperty(alias) && Ext.isDefined(Ext.global.console)) {
5906                     Ext.global.console.log("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
5907                         "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
5908                 }
5909
5910                 aliasToNameMap[alias] = className;
5911             }
5912
5913             if (!nameToAliasesMap[className]) {
5914                 nameToAliasesMap[className] = [];
5915             }
5916
5917             if (alias) {
5918                 Ext.Array.include(nameToAliasesMap[className], alias);
5919             }
5920
5921             return this;
5922         },
5923
5924         /**
5925          * Get a reference to the class by its alias.
5926          *
5927          * @param {String} alias
5928          * @return {Class} class
5929          */
5930         getByAlias: function(alias) {
5931             return this.get(this.getNameByAlias(alias));
5932         },
5933
5934         /**
5935          * Get the name of a class by its alias.
5936          *
5937          * @param {String} alias
5938          * @return {String} className
5939          */
5940         getNameByAlias: function(alias) {
5941             return this.maps.aliasToName[alias] || '';
5942         },
5943
5944         /**
5945          * Get the name of a class by its alternate name.
5946          *
5947          * @param {String} alternate
5948          * @return {String} className
5949          */
5950         getNameByAlternate: function(alternate) {
5951             return this.maps.alternateToName[alternate] || '';
5952         },
5953
5954         /**
5955          * Get the aliases of a class by the class name
5956          *
5957          * @param {String} name
5958          * @return {Array} aliases
5959          */
5960         getAliasesByName: function(name) {
5961             return this.maps.nameToAliases[name] || [];
5962         },
5963
5964         /**
5965          * Get the name of the class by its reference or its instance;
5966          * usually invoked by the shorthand {@link Ext#getClassName Ext.getClassName}
5967
5968     Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
5969
5970          * @param {Class/Object} object
5971          * @return {String} className
5972          * @markdown
5973          */
5974         getName: function(object) {
5975             return object && object.$className || '';
5976         },
5977
5978         /**
5979          * Get the class of the provided object; returns null if it's not an instance
5980          * of any class created with Ext.define. This is usually invoked by the shorthand {@link Ext#getClass Ext.getClass}
5981          *
5982     var component = new Ext.Component();
5983
5984     Ext.ClassManager.getClass(component); // returns Ext.Component
5985              *
5986          * @param {Object} object
5987          * @return {Class} class
5988          * @markdown
5989          */
5990         getClass: function(object) {
5991             return object && object.self || null;
5992         },
5993
5994         /**
5995          * Defines a class. This is usually invoked via the alias {@link Ext#define Ext.define}
5996
5997     Ext.ClassManager.create('My.awesome.Class', {
5998         someProperty: 'something',
5999         someMethod: function() { ... }
6000         ...
6001
6002     }, function() {
6003         alert('Created!');
6004         alert(this === My.awesome.Class); // alerts true
6005
6006         var myInstance = new this();
6007     });
6008
6009          * @param {String} className The class name to create in string dot-namespaced format, for example:
6010          * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
6011          * It is highly recommended to follow this simple convention:
6012
6013 - The root and the class name are 'CamelCased'
6014 - Everything else is lower-cased
6015
6016          * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of any valid
6017          * strings, except those in the reserved listed below:
6018
6019 - `mixins`
6020 - `statics`
6021 - `config`
6022 - `alias`
6023 - `self`
6024 - `singleton`
6025 - `alternateClassName`
6026          *
6027          * @param {Function} createdFn Optional callback to execute after the class is created, the execution scope of which
6028          * (`this`) will be the newly created class itself.
6029          * @return {Ext.Base}
6030          * @markdown
6031          */
6032         create: function(className, data, createdFn) {
6033             var manager = this;
6034
6035             if (typeof className !== 'string') {
6036                 Ext.Error.raise({
6037                     sourceClass: "Ext",
6038                     sourceMethod: "define",
6039                     msg: "Invalid class name '" + className + "' specified, must be a non-empty string"
6040                 });
6041             }
6042
6043             data.$className = className;
6044
6045             return new Class(data, function() {
6046                 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
6047                     registeredPostprocessors = manager.postprocessors,
6048                     index = 0,
6049                     postprocessors = [],
6050                     postprocessor, postprocessors, process, i, ln;
6051
6052                 delete data.postprocessors;
6053
6054                 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
6055                     postprocessor = postprocessorStack[i];
6056
6057                     if (typeof postprocessor === 'string') {
6058                         postprocessor = registeredPostprocessors[postprocessor];
6059
6060                         if (!postprocessor.always) {
6061                             if (data[postprocessor.name] !== undefined) {
6062                                 postprocessors.push(postprocessor.fn);
6063                             }
6064                         }
6065                         else {
6066                             postprocessors.push(postprocessor.fn);
6067                         }
6068                     }
6069                     else {
6070                         postprocessors.push(postprocessor);
6071                     }
6072                 }
6073
6074                 process = function(clsName, cls, clsData) {
6075                     postprocessor = postprocessors[index++];
6076
6077                     if (!postprocessor) {
6078                         manager.set(className, cls);
6079
6080                         Ext.Loader.historyPush(className);
6081
6082                         if (createdFn) {
6083                             createdFn.call(cls, cls);
6084                         }
6085
6086                         return;
6087                     }
6088
6089                     if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
6090                         process.apply(this, arguments);
6091                     }
6092                 };
6093
6094                 process.call(manager, className, this, data);
6095             });
6096         },
6097
6098         /**
6099          * Instantiate a class by its alias; usually invoked by the convenient shorthand {@link Ext#createByAlias Ext.createByAlias}
6100          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6101          * attempt to load the class via synchronous loading.
6102
6103     var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
6104
6105          * @param {String} alias
6106          * @param {Mixed} args,... Additional arguments after the alias will be passed to the
6107          * class constructor.
6108          * @return {Object} instance
6109          * @markdown
6110          */
6111         instantiateByAlias: function() {
6112             var alias = arguments[0],
6113                 args = slice.call(arguments),
6114                 className = this.getNameByAlias(alias);
6115
6116             if (!className) {
6117                 className = this.maps.aliasToName[alias];
6118
6119                 if (!className) {
6120                     Ext.Error.raise({
6121                         sourceClass: "Ext",
6122                         sourceMethod: "createByAlias",
6123                         msg: "Cannot create an instance of unrecognized alias: " + alias
6124                     });
6125                 }
6126
6127                 if (Ext.global.console) {
6128                     Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
6129                          "Ext.require('" + alias + "') above Ext.onReady");
6130                 }
6131
6132                 Ext.syncRequire(className);
6133             }
6134
6135             args[0] = className;
6136
6137             return this.instantiate.apply(this, args);
6138         },
6139
6140         /**
6141          * Instantiate a class by either full name, alias or alternate name; usually invoked by the convenient
6142          * shorthand {@link Ext#create Ext.create}
6143          *
6144          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6145          * attempt to load the class via synchronous loading.
6146          *
6147          * For example, all these three lines return the same result:
6148
6149     // alias
6150     var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
6151
6152     // alternate name
6153     var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
6154
6155     // full class name
6156     var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
6157
6158          * @param {String} name
6159          * @param {Mixed} args,... Additional arguments after the name will be passed to the class' constructor.
6160          * @return {Object} instance
6161          * @markdown
6162          */
6163         instantiate: function() {
6164             var name = arguments[0],
6165                 args = slice.call(arguments, 1),
6166                 alias = name,
6167                 possibleName, cls;
6168
6169             if (typeof name !== 'function') {
6170                 if ((typeof name !== 'string' || name.length < 1)) {
6171                     Ext.Error.raise({
6172                         sourceClass: "Ext",
6173                         sourceMethod: "create",
6174                         msg: "Invalid class name or alias '" + name + "' specified, must be a non-empty string"
6175                     });
6176                 }
6177
6178                 cls = this.get(name);
6179             }
6180             else {
6181                 cls = name;
6182             }
6183
6184             // No record of this class name, it's possibly an alias, so look it up
6185             if (!cls) {
6186                 possibleName = this.getNameByAlias(name);
6187
6188                 if (possibleName) {
6189                     name = possibleName;
6190
6191                     cls = this.get(name);
6192                 }
6193             }
6194
6195             // Still no record of this class name, it's possibly an alternate name, so look it up
6196             if (!cls) {
6197                 possibleName = this.getNameByAlternate(name);
6198
6199                 if (possibleName) {
6200                     name = possibleName;
6201
6202                     cls = this.get(name);
6203                 }
6204             }
6205
6206             // Still not existing at this point, try to load it via synchronous mode as the last resort
6207             if (!cls) {
6208                 if (Ext.global.console) {
6209                     Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +
6210                          "Ext.require('" + ((possibleName) ? alias : name) + "') above Ext.onReady");
6211                 }
6212
6213                 Ext.syncRequire(name);
6214
6215                 cls = this.get(name);
6216             }
6217
6218             if (!cls) {
6219                 Ext.Error.raise({
6220                     sourceClass: "Ext",
6221                     sourceMethod: "create",
6222                     msg: "Cannot create an instance of unrecognized class name / alias: " + alias
6223                 });
6224             }
6225
6226             if (typeof cls !== 'function') {
6227                 Ext.Error.raise({
6228                     sourceClass: "Ext",
6229                     sourceMethod: "create",
6230                     msg: "'" + name + "' is a singleton and cannot be instantiated"
6231                 });
6232             }
6233
6234             if (!this.instantiationCounts[name]) {
6235                 this.instantiationCounts[name] = 0;
6236             }
6237
6238             this.instantiationCounts[name]++;
6239
6240             return this.getInstantiator(args.length)(cls, args);
6241         },
6242
6243         /**
6244          * @private
6245          * @param name
6246          * @param args
6247          */
6248         dynInstantiate: function(name, args) {
6249             args = Ext.Array.from(args, true);
6250             args.unshift(name);
6251
6252             return this.instantiate.apply(this, args);
6253         },
6254
6255         /**
6256          * @private
6257          * @param length
6258          */
6259         getInstantiator: function(length) {
6260             if (!this.instantiators[length]) {
6261                 var i = length,
6262                     args = [];
6263
6264                 for (i = 0; i < length; i++) {
6265                     args.push('a['+i+']');
6266                 }
6267
6268                 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
6269             }
6270
6271             return this.instantiators[length];
6272         },
6273
6274         /**
6275          * @private
6276          */
6277         postprocessors: {},
6278
6279         /**
6280          * @private
6281          */
6282         defaultPostprocessors: [],
6283
6284         /**
6285          * Register a post-processor function.
6286          *
6287          * @param {String} name
6288          * @param {Function} postprocessor
6289          */
6290         registerPostprocessor: function(name, fn, always) {
6291             this.postprocessors[name] = {
6292                 name: name,
6293                 always: always ||  false,
6294                 fn: fn
6295             };
6296
6297             return this;
6298         },
6299
6300         /**
6301          * Set the default post processors array stack which are applied to every class.
6302          *
6303          * @param {String/Array} The name of a registered post processor or an array of registered names.
6304          * @return {Ext.ClassManager} this
6305          */
6306         setDefaultPostprocessors: function(postprocessors) {
6307             this.defaultPostprocessors = Ext.Array.from(postprocessors);
6308
6309             return this;
6310         },
6311
6312         /**
6313          * Insert this post-processor at a specific position in the stack, optionally relative to
6314          * any existing post-processor
6315          *
6316          * @param {String} name The post-processor name. Note that it needs to be registered with
6317          * {@link Ext.ClassManager#registerPostprocessor} before this
6318          * @param {String} offset The insertion position. Four possible values are:
6319          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
6320          * @param {String} relativeName
6321          * @return {Ext.ClassManager} this
6322          */
6323         setDefaultPostprocessorPosition: function(name, offset, relativeName) {
6324             var defaultPostprocessors = this.defaultPostprocessors,
6325                 index;
6326
6327             if (typeof offset === 'string') {
6328                 if (offset === 'first') {
6329                     defaultPostprocessors.unshift(name);
6330
6331                     return this;
6332                 }
6333                 else if (offset === 'last') {
6334                     defaultPostprocessors.push(name);
6335
6336                     return this;
6337                 }
6338
6339                 offset = (offset === 'after') ? 1 : -1;
6340             }
6341
6342             index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
6343
6344             if (index !== -1) {
6345                 defaultPostprocessors.splice(Math.max(0, index + offset), 0, name);
6346             }
6347
6348             return this;
6349         },
6350
6351         /**
6352          * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
6353          * or class names. Expressions support wildcards:
6354
6355      // returns ['Ext.window.Window']
6356     var window = Ext.ClassManager.getNamesByExpression('widget.window');
6357
6358     // returns ['widget.panel', 'widget.window', ...]
6359     var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
6360
6361     // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
6362     var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
6363
6364          * @param {String} expression
6365          * @return {Array} classNames
6366          * @markdown
6367          */
6368         getNamesByExpression: function(expression) {
6369             var nameToAliasesMap = this.maps.nameToAliases,
6370                 names = [],
6371                 name, alias, aliases, possibleName, regex, i, ln;
6372
6373             if (typeof expression !== 'string' || expression.length < 1) {
6374                 Ext.Error.raise({
6375                     sourceClass: "Ext.ClassManager",
6376                     sourceMethod: "getNamesByExpression",
6377                     msg: "Expression " + expression + " is invalid, must be a non-empty string"
6378                 });
6379             }
6380
6381             if (expression.indexOf('*') !== -1) {
6382                 expression = expression.replace(/\*/g, '(.*?)');
6383                 regex = new RegExp('^' + expression + '$');
6384
6385                 for (name in nameToAliasesMap) {
6386                     if (nameToAliasesMap.hasOwnProperty(name)) {
6387                         aliases = nameToAliasesMap[name];
6388
6389                         if (name.search(regex) !== -1) {
6390                             names.push(name);
6391                         }
6392                         else {
6393                             for (i = 0, ln = aliases.length; i < ln; i++) {
6394                                 alias = aliases[i];
6395
6396                                 if (alias.search(regex) !== -1) {
6397                                     names.push(name);
6398                                     break;
6399                                 }
6400                             }
6401                         }
6402                     }
6403                 }
6404
6405             } else {
6406                 possibleName = this.getNameByAlias(expression);
6407
6408                 if (possibleName) {
6409                     names.push(possibleName);
6410                 } else {
6411                     possibleName = this.getNameByAlternate(expression);
6412
6413                     if (possibleName) {
6414                         names.push(possibleName);
6415                     } else {
6416                         names.push(expression);
6417                     }
6418                 }
6419             }
6420
6421             return names;
6422         }
6423     };
6424
6425     Manager.registerPostprocessor('alias', function(name, cls, data) {
6426         var aliases = data.alias,
6427             widgetPrefix = 'widget.',
6428             i, ln, alias;
6429
6430         if (!(aliases instanceof Array)) {
6431             aliases = [aliases];
6432         }
6433
6434         for (i = 0, ln = aliases.length; i < ln; i++) {
6435             alias = aliases[i];
6436
6437             if (typeof alias !== 'string') {
6438                 Ext.Error.raise({
6439                     sourceClass: "Ext",
6440                     sourceMethod: "define",
6441                     msg: "Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string"
6442                 });
6443             }
6444
6445             this.setAlias(cls, alias);
6446         }
6447
6448         // This is ugly, will change to make use of parseNamespace for alias later on
6449         for (i = 0, ln = aliases.length; i < ln; i++) {
6450             alias = aliases[i];
6451
6452             if (alias.substring(0, widgetPrefix.length) === widgetPrefix) {
6453                 // Only the first alias with 'widget.' prefix will be used for xtype
6454                 cls.xtype = cls.$xtype = alias.substring(widgetPrefix.length);
6455                 break;
6456             }
6457         }
6458     });
6459
6460     Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
6461         fn.call(this, name, new cls(), data);
6462         return false;
6463     });
6464
6465     Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
6466         var alternates = data.alternateClassName,
6467             i, ln, alternate;
6468
6469         if (!(alternates instanceof Array)) {
6470             alternates = [alternates];
6471         }
6472
6473         for (i = 0, ln = alternates.length; i < ln; i++) {
6474             alternate = alternates[i];
6475
6476             if (typeof alternate !== 'string') {
6477                 Ext.Error.raise({
6478                     sourceClass: "Ext",
6479                     sourceMethod: "define",
6480                     msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"
6481                 });
6482             }
6483
6484             this.set(alternate, cls);
6485         }
6486     });
6487
6488     Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
6489
6490     Ext.apply(Ext, {
6491         /**
6492          * Convenient shorthand, see {@link Ext.ClassManager#instantiate}
6493          * @member Ext
6494          * @method create
6495          */
6496         create: alias(Manager, 'instantiate'),
6497
6498         /**
6499          * @private
6500          * API to be stablized
6501          *
6502          * @param {Mixed} item
6503          * @param {String} namespace
6504          */
6505         factory: function(item, namespace) {
6506             if (item instanceof Array) {
6507                 var i, ln;
6508
6509                 for (i = 0, ln = item.length; i < ln; i++) {
6510                     item[i] = Ext.factory(item[i], namespace);
6511                 }
6512
6513                 return item;
6514             }
6515
6516             var isString = (typeof item === 'string');
6517
6518             if (isString || (item instanceof Object && item.constructor === Object)) {
6519                 var name, config = {};
6520
6521                 if (isString) {
6522                     name = item;
6523                 }
6524                 else {
6525                     name = item.className;
6526                     config = item;
6527                     delete config.className;
6528                 }
6529
6530                 if (namespace !== undefined && name.indexOf(namespace) === -1) {
6531                     name = namespace + '.' + Ext.String.capitalize(name);
6532                 }
6533
6534                 return Ext.create(name, config);
6535             }
6536
6537             if (typeof item === 'function') {
6538                 return Ext.create(item);
6539             }
6540
6541             return item;
6542         },
6543
6544         /**
6545          * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
6546
6547     var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
6548     var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
6549
6550          * @member Ext
6551          * @method widget
6552          * @markdown
6553          */
6554         widget: function(name) {
6555             var args = slice.call(arguments);
6556             args[0] = 'widget.' + name;
6557
6558             return Manager.instantiateByAlias.apply(Manager, args);
6559         },
6560
6561         /**
6562          * Convenient shorthand, see {@link Ext.ClassManager#instantiateByAlias}
6563          * @member Ext
6564          * @method createByAlias
6565          */
6566         createByAlias: alias(Manager, 'instantiateByAlias'),
6567
6568         /**
6569          * Convenient shorthand for {@link Ext.ClassManager#create}, see detailed {@link Ext.Class explanation}
6570          * @member Ext
6571          * @method define
6572          */
6573         define: alias(Manager, 'create'),
6574
6575         /**
6576          * Convenient shorthand, see {@link Ext.ClassManager#getName}
6577          * @member Ext
6578          * @method getClassName
6579          */
6580         getClassName: alias(Manager, 'getName'),
6581
6582         /**
6583          *
6584          * @param {Mixed} object
6585          */
6586         getDisplayName: function(object) {
6587             if (object.displayName) {
6588                 return object.displayName;
6589             }
6590
6591             if (object.$name && object.$class) {
6592                 return Ext.getClassName(object.$class) + '#' + object.$name;
6593             }
6594
6595             if (object.$className) {
6596                 return object.$className;
6597             }
6598
6599             return 'Anonymous';
6600         },
6601
6602         /**
6603          * Convenient shorthand, see {@link Ext.ClassManager#getClass}
6604          * @member Ext
6605          * @method getClassName
6606          */
6607         getClass: alias(Manager, 'getClass'),
6608
6609         /**
6610          * Creates namespaces to be used for scoping variables and classes so that they are not global.
6611          * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
6612
6613     Ext.namespace('Company', 'Company.data');
6614
6615      // equivalent and preferable to the above syntax
6616     Ext.namespace('Company.data');
6617
6618     Company.Widget = function() { ... };
6619
6620     Company.data.CustomStore = function(config) { ... };
6621
6622          * @param {String} namespace1
6623          * @param {String} namespace2
6624          * @param {String} etc
6625          * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
6626          * @function
6627          * @member Ext
6628          * @method namespace
6629          * @markdown
6630          */
6631         namespace: alias(Manager, 'createNamespaces')
6632     });
6633
6634     Ext.createWidget = Ext.widget;
6635
6636     /**
6637      * Convenient alias for {@link Ext#namespace Ext.namespace}
6638      * @member Ext
6639      * @method ns
6640      */
6641     Ext.ns = Ext.namespace;
6642
6643     Class.registerPreprocessor('className', function(cls, data) {
6644         if (data.$className) {
6645             cls.$className = data.$className;
6646             cls.displayName = cls.$className;
6647         }
6648     }, true);
6649
6650     Class.setDefaultPreprocessorPosition('className', 'first');
6651
6652 })(Ext.Class, Ext.Function.alias);
6653
6654 /**
6655  * @author Jacky Nguyen <jacky@sencha.com>
6656  * @docauthor Jacky Nguyen <jacky@sencha.com>
6657  * @class Ext.Loader
6658  *
6659
6660 Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
6661 via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
6662 approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons of each approach:
6663
6664 # Asynchronous Loading #
6665
6666 - Advantages:
6667         + Cross-domain
6668         + No web server needed: you can run the application via the file system protocol (i.e: `file://path/to/your/index
6669  .html`)
6670         + Best possible debugging experience: error messages come with the exact file name and line number
6671
6672 - Disadvantages:
6673         + Dependencies need to be specified before-hand
6674
6675 ### Method 1: Explicitly include what you need: ###
6676
6677     // Syntax
6678     Ext.require({String/Array} expressions);
6679
6680     // Example: Single alias
6681     Ext.require('widget.window');
6682
6683     // Example: Single class name
6684     Ext.require('Ext.window.Window');
6685
6686     // Example: Multiple aliases / class names mix
6687     Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
6688
6689     // Wildcards
6690     Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
6691
6692 ### Method 2: Explicitly exclude what you don't need: ###
6693
6694     // Syntax: Note that it must be in this chaining format.
6695     Ext.exclude({String/Array} expressions)
6696        .require({String/Array} expressions);
6697
6698     // Include everything except Ext.data.*
6699     Ext.exclude('Ext.data.*').require('*'); 
6700
6701     // Include all widgets except widget.checkbox*,
6702     // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
6703     Ext.exclude('widget.checkbox*').require('widget.*');
6704
6705 # Synchronous Loading on Demand #
6706
6707 - *Advantages:*
6708         + There's no need to specify dependencies before-hand, which is always the convenience of including ext-all.js
6709  before
6710
6711 - *Disadvantages:*
6712         + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
6713         + Must be from the same domain due to XHR restriction
6714         + Need a web server, same reason as above
6715
6716 There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
6717
6718     Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
6719
6720     Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
6721
6722     Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
6723
6724 Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
6725  existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load the given
6726  class and all its dependencies.
6727
6728 # Hybrid Loading - The Best of Both Worlds #
6729
6730 It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
6731
6732 ### Step 1: Start writing your application using synchronous approach. Ext.Loader will automatically fetch all
6733  dependencies on demand as they're needed during run-time. For example: ###
6734
6735     Ext.onReady(function(){
6736         var window = Ext.createWidget('window', {
6737             width: 500,
6738             height: 300,
6739             layout: {
6740                 type: 'border',
6741                 padding: 5
6742             },
6743             title: 'Hello Dialog',
6744             items: [{
6745                 title: 'Navigation',
6746                 collapsible: true,
6747                 region: 'west',
6748                 width: 200,
6749                 html: 'Hello',
6750                 split: true
6751             }, {
6752                 title: 'TabPanel',
6753                 region: 'center'
6754             }]
6755         });
6756
6757         window.show();
6758     })
6759
6760 ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these: ###
6761
6762     [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code
6763     ClassManager.js:432
6764     [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
6765
6766 Simply copy and paste the suggested code above `Ext.onReady`, i.e:
6767
6768     Ext.require('Ext.window.Window');
6769     Ext.require('Ext.layout.container.Border');
6770
6771     Ext.onReady(...);
6772
6773 Everything should now load via asynchronous mode.
6774
6775 # Deployment #
6776
6777 It's important to note that dynamic loading should only be used during development on your local machines.
6778 During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
6779 the whole process of transitioning from / to between development / maintenance and production as easy as
6780 possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies your application
6781 needs in the exact loading sequence. It's as simple as concatenating all files in this array into one,
6782 then include it on top of your application.
6783
6784 This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
6785
6786  * @singleton
6787  * @markdown
6788  */
6789
6790 (function(Manager, Class, flexSetter, alias) {
6791
6792     var
6793         dependencyProperties = ['extend', 'mixins', 'requires'],
6794         Loader;
6795
6796     Loader = Ext.Loader = {
6797         /**
6798          * @private
6799          */
6800         documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
6801
6802         /**
6803          * Flag indicating whether there are still files being loaded
6804          * @private
6805          */
6806         isLoading: false,
6807
6808         /**
6809          * Maintain the queue for all dependencies. Each item in the array is an object of the format:
6810          * {
6811          *      requires: [...], // The required classes for this queue item
6812          *      callback: function() { ... } // The function to execute when all classes specified in requires exist
6813          * }
6814          * @private
6815          */
6816         queue: [],
6817
6818         /**
6819          * Maintain the list of files that have already been handled so that they never get double-loaded
6820          * @private
6821          */
6822         isFileLoaded: {},
6823
6824         /**
6825          * Maintain the list of listeners to execute when all required scripts are fully loaded
6826          * @private
6827          */
6828         readyListeners: [],
6829
6830         /**
6831          * Contains optional dependencies to be loaded last
6832          * @private
6833          */
6834         optionalRequires: [],
6835
6836         /**
6837          * Map of fully qualified class names to an array of dependent classes.
6838          * @private
6839          */
6840         requiresMap: {},
6841
6842         /**
6843          * @private
6844          */
6845         numPendingFiles: 0,
6846
6847         /**
6848          * @private
6849          */
6850         numLoadedFiles: 0,
6851
6852         /** @private */
6853         hasFileLoadError: false,
6854
6855         /**
6856          * @private
6857          */
6858         classNameToFilePathMap: {},
6859
6860         /**
6861          * An array of class names to keep track of the dependency loading order.
6862          * This is not guaranteed to be the same everytime due to the asynchronous
6863          * nature of the Loader.
6864          *
6865          * @property history
6866          * @type Array
6867          */
6868         history: [],
6869
6870         /**
6871          * Configuration
6872          * @private
6873          */
6874         config: {
6875             /**
6876              * Whether or not to enable the dynamic dependency loading feature
6877              * Defaults to false
6878              * @cfg {Boolean} enabled
6879              */
6880             enabled: false,
6881
6882             /**
6883              * @cfg {Boolean} disableCaching
6884              * Appends current timestamp to script files to prevent caching
6885              * Defaults to true
6886              */
6887             disableCaching: true,
6888
6889             /**
6890              * @cfg {String} disableCachingParam
6891              * The get parameter name for the cache buster's timestamp.
6892              * Defaults to '_dc'
6893              */
6894             disableCachingParam: '_dc',
6895
6896             /**
6897              * @cfg {Object} paths
6898              * The mapping from namespaces to file paths
6899     {
6900         'Ext': '.', // This is set by default, Ext.layout.container.Container will be
6901                     // loaded from ./layout/Container.js
6902
6903         'My': './src/my_own_folder' // My.layout.Container will be loaded from
6904                                     // ./src/my_own_folder/layout/Container.js
6905     }
6906              * Note that all relative paths are relative to the current HTML document.
6907              * If not being specified, for example, <code>Other.awesome.Class</code>
6908              * will simply be loaded from <code>./Other/awesome/Class.js</code>
6909              */
6910             paths: {
6911                 'Ext': '.'
6912             }
6913         },
6914
6915         /**
6916          * Set the configuration for the loader. This should be called right after ext-core.js
6917          * (or ext-core-debug.js) is included in the page, i.e:
6918
6919     <script type="text/javascript" src="ext-core-debug.js"></script>
6920     <script type="text/javascript">
6921       Ext.Loader.setConfig({
6922           enabled: true,
6923           paths: {
6924               'My': 'my_own_path'
6925           }
6926       });
6927     <script>
6928     <script type="text/javascript">
6929       Ext.require(...);
6930
6931       Ext.onReady(function() {
6932           // application code here
6933       });
6934     </script>
6935
6936          * Refer to {@link Ext.Loader#configs} for the list of possible properties
6937          *
6938          * @param {Object} config The config object to override the default values in {@link Ext.Loader#config}
6939          * @return {Ext.Loader} this
6940          * @markdown
6941          */
6942         setConfig: function(name, value) {
6943             if (Ext.isObject(name) && arguments.length === 1) {
6944                 Ext.Object.merge(this.config, name);
6945             }
6946             else {
6947                 this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
6948             }
6949
6950             return this;
6951         },
6952
6953         /**
6954          * Get the config value corresponding to the specified name. If no name is given, will return the config object
6955          * @param {String} name The config property name
6956          * @return {Object/Mixed}
6957          */
6958         getConfig: function(name) {
6959             if (name) {
6960                 return this.config[name];
6961             }
6962
6963             return this.config;
6964         },
6965
6966         /**
6967          * Sets the path of a namespace.
6968          * For Example:
6969
6970     Ext.Loader.setPath('Ext', '.');
6971
6972          * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
6973          * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
6974          * @return {Ext.Loader} this
6975          * @method
6976          * @markdown
6977          */
6978         setPath: flexSetter(function(name, path) {
6979             this.config.paths[name] = path;
6980
6981             return this;
6982         }),
6983
6984         /**
6985          * Translates a className to a file path by adding the
6986          * the proper prefix and converting the .'s to /'s. For example:
6987
6988     Ext.Loader.setPath('My', '/path/to/My');
6989
6990     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
6991
6992          * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
6993
6994     Ext.Loader.setPath({
6995         'My': '/path/to/lib',
6996         'My.awesome': '/other/path/for/awesome/stuff',
6997         'My.awesome.more': '/more/awesome/path'
6998     });
6999
7000     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
7001
7002     alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
7003
7004     alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
7005
7006     alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
7007
7008          * @param {String} className
7009          * @return {String} path
7010          * @markdown
7011          */
7012         getPath: function(className) {
7013             var path = '',
7014                 paths = this.config.paths,
7015                 prefix = this.getPrefix(className);
7016
7017             if (prefix.length > 0) {
7018                 if (prefix === className) {
7019                     return paths[prefix];
7020                 }
7021
7022                 path = paths[prefix];
7023                 className = className.substring(prefix.length + 1);
7024             }
7025
7026             if (path.length > 0) {
7027                 path += '/';
7028             }
7029
7030             return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
7031         },
7032
7033         /**
7034          * @private
7035          * @param {String} className
7036          */
7037         getPrefix: function(className) {
7038             var paths = this.config.paths,
7039                 prefix, deepestPrefix = '';
7040
7041             if (paths.hasOwnProperty(className)) {
7042                 return className;
7043             }
7044
7045             for (prefix in paths) {
7046                 if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
7047                     if (prefix.length > deepestPrefix.length) {
7048                         deepestPrefix = prefix;
7049                     }
7050                 }
7051             }
7052
7053             return deepestPrefix;
7054         },
7055
7056         /**
7057          * Refresh all items in the queue. If all dependencies for an item exist during looping,
7058          * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
7059          * empty
7060          * @private
7061          */
7062         refreshQueue: function() {
7063             var ln = this.queue.length,
7064                 i, item, j, requires;
7065
7066             if (ln === 0) {
7067                 this.triggerReady();
7068                 return;
7069             }
7070
7071             for (i = 0; i < ln; i++) {
7072                 item = this.queue[i];
7073
7074                 if (item) {
7075                     requires = item.requires;
7076
7077                     // Don't bother checking when the number of files loaded
7078                     // is still less than the array length
7079                     if (requires.length > this.numLoadedFiles) {
7080                         continue;
7081                     }
7082
7083                     j = 0;
7084
7085                     do {
7086                         if (Manager.isCreated(requires[j])) {
7087                             // Take out from the queue
7088                             requires.splice(j, 1);
7089                         }
7090                         else {
7091                             j++;
7092                         }
7093                     } while (j < requires.length);
7094
7095                     if (item.requires.length === 0) {
7096                         this.queue.splice(i, 1);
7097                         item.callback.call(item.scope);
7098                         this.refreshQueue();
7099                         break;
7100                     }
7101                 }
7102             }
7103
7104             return this;
7105         },
7106
7107         /**
7108          * Inject a script element to document's head, call onLoad and onError accordingly
7109          * @private
7110          */
7111         injectScriptElement: function(url, onLoad, onError, scope) {
7112             var script = document.createElement('script'),
7113                 me = this,
7114                 onLoadFn = function() {
7115                     me.cleanupScriptElement(script);
7116                     onLoad.call(scope);
7117                 },
7118                 onErrorFn = function() {
7119                     me.cleanupScriptElement(script);
7120                     onError.call(scope);
7121                 };
7122
7123             script.type = 'text/javascript';
7124             script.src = url;
7125             script.onload = onLoadFn;
7126             script.onerror = onErrorFn;
7127             script.onreadystatechange = function() {
7128                 if (this.readyState === 'loaded' || this.readyState === 'complete') {
7129                     onLoadFn();
7130                 }
7131             };
7132
7133             this.documentHead.appendChild(script);
7134
7135             return script;
7136         },
7137
7138         /**
7139          * @private
7140          */
7141         cleanupScriptElement: function(script) {
7142             script.onload = null;
7143             script.onreadystatechange = null;
7144             script.onerror = null;
7145
7146             return this;
7147         },
7148
7149         /**
7150          * Load a script file, supports both asynchronous and synchronous approaches
7151          *
7152          * @param {String} url
7153          * @param {Function} onLoad
7154          * @param {Scope} scope
7155          * @param {Boolean} synchronous
7156          * @private
7157          */
7158         loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
7159             var me = this,
7160                 noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
7161                 fileName = url.split('/').pop(),
7162                 isCrossOriginRestricted = false,
7163                 xhr, status, onScriptError;
7164
7165             scope = scope || this;
7166
7167             this.isLoading = true;
7168
7169             if (!synchronous) {
7170                 onScriptError = function() {
7171                     onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
7172                 };
7173
7174                 if (!Ext.isReady && Ext.onDocumentReady) {
7175                     Ext.onDocumentReady(function() {
7176                         me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7177                     });
7178                 }
7179                 else {
7180                     this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7181                 }
7182             }
7183             else {
7184                 if (typeof XMLHttpRequest !== 'undefined') {
7185                     xhr = new XMLHttpRequest();
7186                 } else {
7187                     xhr = new ActiveXObject('Microsoft.XMLHTTP');
7188                 }
7189
7190                 try {
7191                     xhr.open('GET', noCacheUrl, false);
7192                     xhr.send(null);
7193                 } catch (e) {
7194                     isCrossOriginRestricted = true;
7195                 }
7196
7197                 status = (xhr.status === 1223) ? 204 : xhr.status;
7198
7199                 if (!isCrossOriginRestricted) {
7200                     isCrossOriginRestricted = (status === 0);
7201                 }
7202
7203                 if (isCrossOriginRestricted
7204                 ) {
7205                     onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
7206                                        "being loaded from a different domain or from the local file system whereby cross origin " +
7207                                        "requests are not allowed due to security reasons. Use asynchronous loading with " +
7208                                        "Ext.require instead.", synchronous);
7209                 }
7210                 else if (status >= 200 && status < 300
7211                 ) {
7212                     // Firebug friendly, file names are still shown even though they're eval'ed code
7213                     new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();
7214
7215                     onLoad.call(scope);
7216                 }
7217                 else {
7218                     onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
7219                                        "verify that the file exists. " +
7220                                        "XHR status code: " + status, synchronous);
7221                 }
7222
7223                 // Prevent potential IE memory leak
7224                 xhr = null;
7225             }
7226         },
7227
7228         /**
7229          * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
7230          * Can be chained with more `require` and `exclude` methods, eg:
7231
7232     Ext.exclude('Ext.data.*').require('*');
7233
7234     Ext.exclude('widget.button*').require('widget.*');
7235
7236          * @param {Array} excludes
7237          * @return {Object} object contains `require` method for chaining
7238          * @markdown
7239          */
7240         exclude: function(excludes) {
7241             var me = this;
7242
7243             return {
7244                 require: function(expressions, fn, scope) {
7245                     return me.require(expressions, fn, scope, excludes);
7246                 },
7247
7248                 syncRequire: function(expressions, fn, scope) {
7249                     return me.syncRequire(expressions, fn, scope, excludes);
7250                 }
7251             };
7252         },
7253
7254         /**
7255          * Synchronously loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when finishes, within the optional scope. This method is aliased by {@link Ext#syncRequire} for convenience
7256          * @param {String/Array} expressions Can either be a string or an array of string
7257          * @param {Function} fn (Optional) The callback function
7258          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7259          * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
7260          * @markdown
7261          */
7262         syncRequire: function() {
7263             this.syncModeEnabled = true;
7264             this.require.apply(this, arguments);
7265             this.refreshQueue();
7266             this.syncModeEnabled = false;
7267         },
7268
7269         /**
7270          * Loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when
7271          * finishes, within the optional scope. This method is aliased by {@link Ext#require Ext.require} for convenience
7272          * @param {String/Array} expressions Can either be a string or an array of string
7273          * @param {Function} fn (Optional) The callback function
7274          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7275          * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
7276          * @markdown
7277          */
7278         require: function(expressions, fn, scope, excludes) {
7279             var filePath, expression, exclude, className, excluded = {},
7280                 excludedClassNames = [],
7281                 possibleClassNames = [],
7282                 possibleClassName, classNames = [],
7283                 i, j, ln, subLn;
7284
7285             expressions = Ext.Array.from(expressions);
7286             excludes = Ext.Array.from(excludes);
7287
7288             fn = fn || Ext.emptyFn;
7289
7290             scope = scope || Ext.global;
7291
7292             for (i = 0, ln = excludes.length; i < ln; i++) {
7293                 exclude = excludes[i];
7294
7295                 if (typeof exclude === 'string' && exclude.length > 0) {
7296                     excludedClassNames = Manager.getNamesByExpression(exclude);
7297
7298                     for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
7299                         excluded[excludedClassNames[j]] = true;
7300                     }
7301                 }
7302             }
7303
7304             for (i = 0, ln = expressions.length; i < ln; i++) {
7305                 expression = expressions[i];
7306
7307                 if (typeof expression === 'string' && expression.length > 0) {
7308                     possibleClassNames = Manager.getNamesByExpression(expression);
7309
7310                     for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
7311                         possibleClassName = possibleClassNames[j];
7312
7313                         if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
7314                             Ext.Array.include(classNames, possibleClassName);
7315                         }
7316                     }
7317                 }
7318             }
7319
7320             // If the dynamic dependency feature is not being used, throw an error
7321             // if the dependencies are not defined
7322             if (!this.config.enabled) {
7323                 if (classNames.length > 0) {
7324                     Ext.Error.raise({
7325                         sourceClass: "Ext.Loader",
7326                         sourceMethod: "require",
7327                         msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
7328                              "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
7329                     });
7330                 }
7331             }
7332
7333             if (classNames.length === 0) {
7334                 fn.call(scope);
7335                 return this;
7336             }
7337
7338             this.queue.push({
7339                 requires: classNames,
7340                 callback: fn,
7341                 scope: scope
7342             });
7343
7344             classNames = classNames.slice();
7345
7346             for (i = 0, ln = classNames.length; i < ln; i++) {
7347                 className = classNames[i];
7348
7349                 if (!this.isFileLoaded.hasOwnProperty(className)) {
7350                     this.isFileLoaded[className] = false;
7351
7352                     filePath = this.getPath(className);
7353
7354                     this.classNameToFilePathMap[className] = filePath;
7355
7356                     this.numPendingFiles++;
7357
7358                     this.loadScriptFile(
7359                         filePath,
7360                         Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
7361                         Ext.Function.pass(this.onFileLoadError, [className, filePath]),
7362                         this,
7363                         this.syncModeEnabled
7364                     );
7365                 }
7366             }
7367
7368             return this;
7369         },
7370
7371         /**
7372          * @private
7373          * @param {String} className
7374          * @param {String} filePath
7375          */
7376         onFileLoaded: function(className, filePath) {
7377             this.numLoadedFiles++;
7378
7379             this.isFileLoaded[className] = true;
7380
7381             this.numPendingFiles--;
7382
7383             if (this.numPendingFiles === 0) {
7384                 this.refreshQueue();
7385             }
7386
7387             if (this.numPendingFiles <= 1) {
7388                 window.status = "Finished loading all dependencies, onReady fired!";
7389             }
7390             else {
7391                 window.status = "Loading dependencies, " + this.numPendingFiles + " files left...";
7392             }
7393
7394             if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
7395                 var queue = this.queue,
7396                     requires,
7397                     i, ln, j, subLn, missingClasses = [], missingPaths = [];
7398
7399                 for (i = 0, ln = queue.length; i < ln; i++) {
7400                     requires = queue[i].requires;
7401
7402                     for (j = 0, subLn = requires.length; j < ln; j++) {
7403                         if (this.isFileLoaded[requires[j]]) {
7404                             missingClasses.push(requires[j]);
7405                         }
7406                     }
7407                 }
7408
7409                 if (missingClasses.length < 1) {
7410                     return;
7411                 }
7412
7413                 missingClasses = Ext.Array.filter(missingClasses, function(item) {
7414                     return !this.requiresMap.hasOwnProperty(item);
7415                 }, this);
7416
7417                 for (i = 0,ln = missingClasses.length; i < ln; i++) {
7418                     missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
7419                 }
7420
7421                 Ext.Error.raise({
7422                     sourceClass: "Ext.Loader",
7423                     sourceMethod: "onFileLoaded",
7424                     msg: "The following classes are not declared even if their files have been " +
7425                             "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
7426                             "corresponding files for possible typos: '" + missingPaths.join("', '") + "'"
7427                 });
7428             }
7429         },
7430
7431         /**
7432          * @private
7433          */
7434         onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
7435             this.numPendingFiles--;
7436             this.hasFileLoadError = true;
7437
7438             Ext.Error.raise({
7439                 sourceClass: "Ext.Loader",
7440                 classToLoad: className,
7441                 loadPath: filePath,
7442                 loadingType: isSynchronous ? 'synchronous' : 'async',
7443                 msg: errorMessage
7444             });
7445         },
7446
7447         /**
7448          * @private
7449          */
7450         addOptionalRequires: function(requires) {
7451             var optionalRequires = this.optionalRequires,
7452                 i, ln, require;
7453
7454             requires = Ext.Array.from(requires);
7455
7456             for (i = 0, ln = requires.length; i < ln; i++) {
7457                 require = requires[i];
7458
7459                 Ext.Array.include(optionalRequires, require);
7460             }
7461
7462             return this;
7463         },
7464
7465         /**
7466          * @private
7467          */
7468         triggerReady: function(force) {
7469             var readyListeners = this.readyListeners,
7470                 optionalRequires, listener;
7471
7472             if (this.isLoading || force) {
7473                 this.isLoading = false;
7474
7475                 if (this.optionalRequires.length) {
7476                     // Clone then empty the array to eliminate potential recursive loop issue
7477                     optionalRequires = Ext.Array.clone(this.optionalRequires);
7478
7479                     // Empty the original array
7480                     this.optionalRequires.length = 0;
7481
7482                     this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
7483                     return this;
7484                 }
7485
7486                 while (readyListeners.length) {
7487                     listener = readyListeners.shift();
7488                     listener.fn.call(listener.scope);
7489
7490                     if (this.isLoading) {
7491                         return this;
7492                     }
7493                 }
7494             }
7495
7496             return this;
7497         },
7498
7499         /**
7500          * Add a new listener to be executed when all required scripts are fully loaded
7501          *
7502          * @param {Function} fn The function callback to be executed
7503          * @param {Object} scope The execution scope (<code>this</code>) of the callback function
7504          * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
7505          */
7506         onReady: function(fn, scope, withDomReady, options) {
7507             var oldFn;
7508
7509             if (withDomReady !== false && Ext.onDocumentReady) {
7510                 oldFn = fn;
7511
7512                 fn = function() {
7513                     Ext.onDocumentReady(oldFn, scope, options);
7514                 };
7515             }
7516
7517             if (!this.isLoading) {
7518                 fn.call(scope);
7519             }
7520             else {
7521                 this.readyListeners.push({
7522                     fn: fn,
7523                     scope: scope
7524                 });
7525             }
7526         },
7527
7528         /**
7529          * @private
7530          * @param {String} className
7531          */
7532         historyPush: function(className) {
7533             if (className && this.isFileLoaded.hasOwnProperty(className)) {
7534                 Ext.Array.include(this.history, className);
7535             }
7536
7537             return this;
7538         }
7539     };
7540
7541     /**
7542      * Convenient alias of {@link Ext.Loader#require}. Please see the introduction documentation of
7543      * {@link Ext.Loader} for examples.
7544      * @member Ext
7545      * @method require
7546      */
7547     Ext.require = alias(Loader, 'require');
7548
7549     /**
7550      * Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}.
7551      *
7552      * @member Ext
7553      * @method syncRequire
7554      */
7555     Ext.syncRequire = alias(Loader, 'syncRequire');
7556
7557     /**
7558      * Convenient shortcut to {@link Ext.Loader#exclude}
7559      * @member Ext
7560      * @method exclude
7561      */
7562     Ext.exclude = alias(Loader, 'exclude');
7563
7564     /**
7565      * @member Ext
7566      * @method onReady
7567      */
7568     Ext.onReady = function(fn, scope, options) {
7569         Loader.onReady(fn, scope, true, options);
7570     };
7571
7572     Class.registerPreprocessor('loader', function(cls, data, continueFn) {
7573         var me = this,
7574             dependencies = [],
7575             className = Manager.getName(cls),
7576             i, j, ln, subLn, value, propertyName, propertyValue;
7577
7578         /*
7579         Basically loop through the dependencyProperties, look for string class names and push
7580         them into a stack, regardless of whether the property's value is a string, array or object. For example:
7581         {
7582               extend: 'Ext.MyClass',
7583               requires: ['Ext.some.OtherClass'],
7584               mixins: {
7585                   observable: 'Ext.util.Observable';
7586               }
7587         }
7588         which will later be transformed into:
7589         {
7590               extend: Ext.MyClass,
7591               requires: [Ext.some.OtherClass],
7592               mixins: {
7593                   observable: Ext.util.Observable;
7594               }
7595         }
7596         */
7597
7598         for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
7599             propertyName = dependencyProperties[i];
7600
7601             if (data.hasOwnProperty(propertyName)) {
7602                 propertyValue = data[propertyName];
7603
7604                 if (typeof propertyValue === 'string') {
7605                     dependencies.push(propertyValue);
7606                 }
7607                 else if (propertyValue instanceof Array) {
7608                     for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
7609                         value = propertyValue[j];
7610
7611                         if (typeof value === 'string') {
7612                             dependencies.push(value);
7613                         }
7614                     }
7615                 }
7616                 else {
7617                     for (j in propertyValue) {
7618                         if (propertyValue.hasOwnProperty(j)) {
7619                             value = propertyValue[j];
7620
7621                             if (typeof value === 'string') {
7622                                 dependencies.push(value);
7623                             }
7624                         }
7625                     }
7626                 }
7627             }
7628         }
7629
7630         if (dependencies.length === 0) {
7631 //            Loader.historyPush(className);
7632             return;
7633         }
7634
7635         var deadlockPath = [],
7636             requiresMap = Loader.requiresMap,
7637             detectDeadlock;
7638
7639         /*
7640         Automatically detect deadlocks before-hand,
7641         will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
7642
7643         - A extends B, then B extends A
7644         - A requires B, B requires C, then C requires A
7645
7646         The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
7647         no matter how deep the path is.
7648         */
7649
7650         if (className) {
7651             requiresMap[className] = dependencies;
7652
7653             detectDeadlock = function(cls) {
7654                 deadlockPath.push(cls);
7655
7656                 if (requiresMap[cls]) {
7657                     if (Ext.Array.contains(requiresMap[cls], className)) {
7658                         Ext.Error.raise({
7659                             sourceClass: "Ext.Loader",
7660                             msg: "Deadlock detected while loading dependencies! '" + className + "' and '" +
7661                                 deadlockPath[1] + "' " + "mutually require each other. Path: " +
7662                                 deadlockPath.join(' -> ') + " -> " + deadlockPath[0]
7663                         });
7664                     }
7665
7666                     for (i = 0, ln = requiresMap[cls].length; i < ln; i++) {
7667                         detectDeadlock(requiresMap[cls][i]);
7668                     }
7669                 }
7670             };
7671
7672             detectDeadlock(className);
7673         }
7674
7675
7676         Loader.require(dependencies, function() {
7677             for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
7678                 propertyName = dependencyProperties[i];
7679
7680                 if (data.hasOwnProperty(propertyName)) {
7681                     propertyValue = data[propertyName];
7682
7683                     if (typeof propertyValue === 'string') {
7684                         data[propertyName] = Manager.get(propertyValue);
7685                     }
7686                     else if (propertyValue instanceof Array) {
7687                         for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
7688                             value = propertyValue[j];
7689
7690                             if (typeof value === 'string') {
7691                                 data[propertyName][j] = Manager.get(value);
7692                             }
7693                         }
7694                     }
7695                     else {
7696                         for (var k in propertyValue) {
7697                             if (propertyValue.hasOwnProperty(k)) {
7698                                 value = propertyValue[k];
7699
7700                                 if (typeof value === 'string') {
7701                                     data[propertyName][k] = Manager.get(value);
7702                                 }
7703                             }
7704                         }
7705                     }
7706                 }
7707             }
7708
7709             continueFn.call(me, cls, data);
7710         });
7711
7712         return false;
7713     }, true);
7714
7715     Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
7716
7717     Manager.registerPostprocessor('uses', function(name, cls, data) {
7718         var uses = Ext.Array.from(data.uses),
7719             items = [],
7720             i, ln, item;
7721
7722         for (i = 0, ln = uses.length; i < ln; i++) {
7723             item = uses[i];
7724
7725             if (typeof item === 'string') {
7726                 items.push(item);
7727             }
7728         }
7729
7730         Loader.addOptionalRequires(items);
7731     });
7732
7733     Manager.setDefaultPostprocessorPosition('uses', 'last');
7734
7735 })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
7736
7737 /**
7738  * @class Ext.Error
7739  * @private
7740  * @extends Error
7741
7742 A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
7743 errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
7744 uses the Ext 4 class system, the Error class can automatically add the source class and method from which
7745 the error was raised. It also includes logic to automatically log the eroor to the console, if available,
7746 with additional metadata about the error. In all cases, the error will always be thrown at the end so that
7747 execution will halt.
7748
7749 Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
7750 handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
7751 although in a real application it's usually a better idea to override the handling function and perform
7752 logging or some other method of reporting the errors in a way that is meaningful to the application.
7753
7754 At its simplest you can simply raise an error as a simple string from within any code:
7755
7756 #Example usage:#
7757
7758     Ext.Error.raise('Something bad happened!');
7759
7760 If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
7761 displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
7762 additional metadata about the error being raised.  The {@link #raise} method can also take a config object.
7763 In this form the `msg` attribute becomes the error description, and any other data added to the config gets
7764 added to the error object and, if the console is available, logged to the console for inspection.
7765
7766 #Example usage:#
7767
7768     Ext.define('Ext.Foo', {
7769         doSomething: function(option){
7770             if (someCondition === false) {
7771                 Ext.Error.raise({
7772                     msg: 'You cannot do that!',
7773                     option: option,   // whatever was passed into the method
7774                     'error code': 100 // other arbitrary info
7775                 });
7776             }
7777         }
7778     });
7779
7780 If a console is available (that supports the `console.dir` function) you'll see console output like:
7781
7782     An error was raised with the following data:
7783     option:         Object { foo: "bar"}
7784         foo:        "bar"
7785     error code:     100
7786     msg:            "You cannot do that!"
7787     sourceClass:   "Ext.Foo"
7788     sourceMethod:  "doSomething"
7789
7790     uncaught exception: You cannot do that!
7791
7792 As you can see, the error will report exactly where it was raised and will include as much information as the
7793 raising code can usefully provide.
7794
7795 If you want to handle all application errors globally you can simply override the static {@link handle} method
7796 and provide whatever handling logic you need. If the method returns true then the error is considered handled
7797 and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
7798
7799 #Example usage:#
7800
7801     Ext.Error.handle = function(err) {
7802         if (err.someProperty == 'NotReallyAnError') {
7803             // maybe log something to the application here if applicable
7804             return true;
7805         }
7806         // any non-true return value (including none) will cause the error to be thrown
7807     }
7808
7809  * Create a new Error object
7810  * @param {Object} config The config object
7811  * @markdown
7812  * @author Brian Moeskau <brian@sencha.com>
7813  * @docauthor Brian Moeskau <brian@sencha.com>
7814  */
7815 Ext.Error = Ext.extend(Error, {
7816     statics: {
7817         /**
7818          * @property ignore
7819 Static flag that can be used to globally disable error reporting to the browser if set to true
7820 (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
7821 and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
7822 be preferable to supply a custom error {@link #handle handling} function instead.
7823
7824 #Example usage:#
7825
7826     Ext.Error.ignore = true;
7827
7828          * @markdown
7829          * @static
7830          */
7831         ignore: false,
7832
7833         /**
7834          * @property notify
7835 Static flag that can be used to globally control error notification to the user. Unlike
7836 Ex.Error.ignore, this does not effect exceptions. They are still thrown. This value can be
7837 set to false to disable the alert notification (default is true for IE6 and IE7).
7838
7839 Only the first error will generate an alert. Internally this flag is set to false when the
7840 first error occurs prior to displaying the alert.
7841
7842 This flag is not used in a release build.
7843
7844 #Example usage:#
7845
7846     Ext.Error.notify = false;
7847
7848          * @markdown
7849          * @static
7850          */
7851         //notify: Ext.isIE6 || Ext.isIE7,
7852
7853         /**
7854 Raise an error that can include additional data and supports automatic console logging if available.
7855 You can pass a string error message or an object with the `msg` attribute which will be used as the
7856 error message. The object can contain any other name-value attributes (or objects) to be logged
7857 along with the error.
7858
7859 Note that after displaying the error message a JavaScript error will ultimately be thrown so that
7860 execution will halt.
7861
7862 #Example usage:#
7863
7864     Ext.Error.raise('A simple string error message');
7865
7866     // or...
7867
7868     Ext.define('Ext.Foo', {
7869         doSomething: function(option){
7870             if (someCondition === false) {
7871                 Ext.Error.raise({
7872                     msg: 'You cannot do that!',
7873                     option: option,   // whatever was passed into the method
7874                     'error code': 100 // other arbitrary info
7875                 });
7876             }
7877         }
7878     });
7879          * @param {String/Object} err The error message string, or an object containing the
7880          * attribute "msg" that will be used as the error message. Any other data included in
7881          * the object will also be logged to the browser console, if available.
7882          * @static
7883          * @markdown
7884          */
7885         raise: function(err){
7886             err = err || {};
7887             if (Ext.isString(err)) {
7888                 err = { msg: err };
7889             }
7890
7891             var method = this.raise.caller;
7892
7893             if (method) {
7894                 if (method.$name) {
7895                     err.sourceMethod = method.$name;
7896                 }
7897                 if (method.$owner) {
7898                     err.sourceClass = method.$owner.$className;
7899                 }
7900             }
7901
7902             if (Ext.Error.handle(err) !== true) {
7903                 var msg = Ext.Error.prototype.toString.call(err);
7904
7905                 Ext.log({
7906                     msg: msg,
7907                     level: 'error',
7908                     dump: err,
7909                     stack: true
7910                 });
7911
7912                 throw new Ext.Error(err);
7913             }
7914         },
7915
7916         /**
7917 Globally handle any Ext errors that may be raised, optionally providing custom logic to
7918 handle different errors individually. Return true from the function to bypass throwing the
7919 error to the browser, otherwise the error will be thrown and execution will halt.
7920
7921 #Example usage:#
7922
7923     Ext.Error.handle = function(err) {
7924         if (err.someProperty == 'NotReallyAnError') {
7925             // maybe log something to the application here if applicable
7926             return true;
7927         }
7928         // any non-true return value (including none) will cause the error to be thrown
7929     }
7930
7931          * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes
7932          * that were originally raised with it, plus properties about the method and class from which
7933          * the error originated (if raised from a class that uses the Ext 4 class system).
7934          * @static
7935          * @markdown
7936          */
7937         handle: function(){
7938             return Ext.Error.ignore;
7939         }
7940     },
7941
7942     // This is the standard property that is the name of the constructor.
7943     name: 'Ext.Error',
7944
7945     /**
7946      * @constructor
7947      * @param {String/Object} config The error message string, or an object containing the
7948      * attribute "msg" that will be used as the error message. Any other data included in
7949      * the object will be applied to the error instance and logged to the browser console, if available.
7950      */
7951     constructor: function(config){
7952         if (Ext.isString(config)) {
7953             config = { msg: config };
7954         }
7955
7956         var me = this;
7957
7958         Ext.apply(me, config);
7959
7960         me.message = me.message || me.msg; // 'message' is standard ('msg' is non-standard)
7961         // note: the above does not work in old WebKit (me.message is readonly) (Safari 4)
7962     },
7963
7964     /**
7965 Provides a custom string representation of the error object. This is an override of the base JavaScript
7966 `Object.toString` method, which is useful so that when logged to the browser console, an error object will
7967 be displayed with a useful message instead of `[object Object]`, the default `toString` result.
7968
7969 The default implementation will include the error message along with the raising class and method, if available,
7970 but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
7971 a particular error instance, if you want to provide a custom description that will show up in the console.
7972      * @markdown
7973      * @return {String} The error message. If raised from within the Ext 4 class system, the error message
7974      * will also include the raising class and method names, if available.
7975      */
7976     toString: function(){
7977         var me = this,
7978             className = me.className ? me.className  : '',
7979             methodName = me.methodName ? '.' + me.methodName + '(): ' : '',
7980             msg = me.msg || '(No description provided)';
7981
7982         return className + methodName + msg;
7983     }
7984 });
7985
7986 /*
7987  * This mechanism is used to notify the user of the first error encountered on the page. This
7988  * was previously internal to Ext.Error.raise and is a desirable feature since errors often
7989  * slip silently under the radar. It cannot live in Ext.Error.raise since there are times
7990  * where exceptions are handled in a try/catch.
7991  */
7992 (function () {
7993     var prevOnError, timer, errors = 0,
7994         extraordinarilyBad = /(out of stack)|(too much recursion)|(stack overflow)|(out of memory)/i,
7995         win = Ext.global;
7996
7997     if (typeof window === 'undefined') {
7998         return; // build system or some such environment...
7999     }
8000
8001     // This method is called to notify the user of the current error status.
8002     function notify () {
8003         var counters = Ext.log.counters,
8004             supports = Ext.supports,
8005             hasOnError = supports && supports.WindowOnError; // TODO - timing
8006
8007         // Put log counters to the status bar (for most browsers):
8008         if (counters && (counters.error + counters.warn + counters.info + counters.log)) {
8009             var msg = [ 'Logged Errors:',counters.error, 'Warnings:',counters.warn,
8010                         'Info:',counters.info, 'Log:',counters.log].join(' ');
8011             if (errors) {
8012                 msg = '*** Errors: ' + errors + ' - ' + msg;
8013             } else if (counters.error) {
8014                 msg = '*** ' + msg;
8015             }
8016             win.status = msg;
8017         }
8018
8019         // Display an alert on the first error:
8020         if (!Ext.isDefined(Ext.Error.notify)) {
8021             Ext.Error.notify = Ext.isIE6 || Ext.isIE7; // TODO - timing
8022         }
8023         if (Ext.Error.notify && (hasOnError ? errors : (counters && counters.error))) {
8024             Ext.Error.notify = false;
8025
8026             if (timer) {
8027                 win.clearInterval(timer); // ticks can queue up so stop...
8028                 timer = null;
8029             }
8030
8031             alert('Unhandled error on page: See console or log');
8032             poll();
8033         }
8034     }
8035
8036     // Sets up polling loop. This is the only way to know about errors in some browsers
8037     // (Opera/Safari) and is the only way to update the status bar for warnings and other
8038     // non-errors.
8039     function poll () {
8040         timer = win.setInterval(notify, 1000);
8041     }
8042
8043     // window.onerror is ideal (esp in IE) because you get full context. This is harmless
8044     // otherwise (never called) which is good because you cannot feature detect it.
8045     prevOnError = win.onerror || Ext.emptyFn;
8046     win.onerror = function (message) {
8047         ++errors;
8048
8049         if (!extraordinarilyBad.test(message)) {
8050             // too much recursion + our alert right now = crash IE
8051             // our polling loop will pick it up even if we don't alert now
8052             notify();
8053         }
8054
8055         return prevOnError.apply(this, arguments);
8056     };
8057     poll();
8058 })();
8059
8060
8061
8062 /*
8063 Ext JS - JavaScript Library
8064 Copyright (c) 2006-2011, Sencha Inc.
8065 All rights reserved.
8066 licensing@sencha.com
8067 */
8068 /**
8069  * @class Ext.JSON
8070  * Modified version of Douglas Crockford"s json.js that doesn"t
8071  * mess with the Object prototype
8072  * http://www.json.org/js.html
8073  * @singleton
8074  */
8075 Ext.JSON = new(function() {
8076     var useHasOwn = !! {}.hasOwnProperty,
8077     isNative = function() {
8078         var useNative = null;
8079
8080         return function() {
8081             if (useNative === null) {
8082                 useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
8083             }
8084
8085             return useNative;
8086         };
8087     }(),
8088     pad = function(n) {
8089         return n < 10 ? "0" + n : n;
8090     },
8091     doDecode = function(json) {
8092         return eval("(" + json + ')');
8093     },
8094     doEncode = function(o) {
8095         if (!Ext.isDefined(o) || o === null) {
8096             return "null";
8097         } else if (Ext.isArray(o)) {
8098             return encodeArray(o);
8099         } else if (Ext.isDate(o)) {
8100             return Ext.JSON.encodeDate(o);
8101         } else if (Ext.isString(o)) {
8102             return encodeString(o);
8103         } else if (typeof o == "number") {
8104             //don't use isNumber here, since finite checks happen inside isNumber
8105             return isFinite(o) ? String(o) : "null";
8106         } else if (Ext.isBoolean(o)) {
8107             return String(o);
8108         } else if (Ext.isObject(o)) {
8109             return encodeObject(o);
8110         } else if (typeof o === "function") {
8111             return "null";
8112         }
8113         return 'undefined';
8114     },
8115     m = {
8116         "\b": '\\b',
8117         "\t": '\\t',
8118         "\n": '\\n',
8119         "\f": '\\f',
8120         "\r": '\\r',
8121         '"': '\\"',
8122         "\\": '\\\\',
8123         '\x0b': '\\u000b' //ie doesn't handle \v
8124     },
8125     charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g,
8126     encodeString = function(s) {
8127         return '"' + s.replace(charToReplace, function(a) {
8128             var c = m[a];
8129             return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
8130         }) + '"';
8131     },
8132     encodeArray = function(o) {
8133         var a = ["[", ""],
8134         // Note empty string in case there are no serializable members.
8135         len = o.length,
8136         i;
8137         for (i = 0; i < len; i += 1) {
8138             a.push(doEncode(o[i]), ',');
8139         }
8140         // Overwrite trailing comma (or empty string)
8141         a[a.length - 1] = ']';
8142         return a.join("");
8143     },
8144     encodeObject = function(o) {
8145         var a = ["{", ""],
8146         // Note empty string in case there are no serializable members.
8147         i;
8148         for (i in o) {
8149             if (!useHasOwn || o.hasOwnProperty(i)) {
8150                 a.push(doEncode(i), ":", doEncode(o[i]), ',');
8151             }
8152         }
8153         // Overwrite trailing comma (or empty string)
8154         a[a.length - 1] = '}';
8155         return a.join("");
8156     };
8157
8158     /**
8159      * <p>Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
8160      * <b>The returned value includes enclosing double quotation marks.</b></p>
8161      * <p>The default return format is "yyyy-mm-ddThh:mm:ss".</p>
8162      * <p>To override this:</p><pre><code>
8163      Ext.JSON.encodeDate = function(d) {
8164      return d.format('"Y-m-d"');
8165      };
8166      </code></pre>
8167      * @param {Date} d The Date to encode
8168      * @return {String} The string literal to use in a JSON string.
8169      */
8170     this.encodeDate = function(o) {
8171         return '"' + o.getFullYear() + "-" 
8172         + pad(o.getMonth() + 1) + "-"
8173         + pad(o.getDate()) + "T"
8174         + pad(o.getHours()) + ":"
8175         + pad(o.getMinutes()) + ":"
8176         + pad(o.getSeconds()) + '"';
8177     };
8178
8179     /**
8180      * Encodes an Object, Array or other value
8181      * @param {Mixed} o The variable to encode
8182      * @return {String} The JSON string
8183      */
8184     this.encode = function() {
8185         var ec;
8186         return function(o) {
8187             if (!ec) {
8188                 // setup encoding function on first access
8189                 ec = isNative() ? JSON.stringify : doEncode;
8190             }
8191             return ec(o);
8192         };
8193     }();
8194
8195
8196     /**
8197      * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
8198      * @param {String} json The JSON string
8199      * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
8200      * @return {Object} The resulting object
8201      */
8202     this.decode = function() {
8203         var dc;
8204         return function(json, safe) {
8205             if (!dc) {
8206                 // setup decoding function on first access
8207                 dc = isNative() ? JSON.parse : doDecode;
8208             }
8209             try {
8210                 return dc(json);
8211             } catch (e) {
8212                 if (safe === true) {
8213                     return null;
8214                 }
8215                 Ext.Error.raise({
8216                     sourceClass: "Ext.JSON",
8217                     sourceMethod: "decode",
8218                     msg: "You're trying to decode and invalid JSON String: " + json
8219                 });
8220             }
8221         };
8222     }();
8223
8224 })();
8225 /**
8226  * Shorthand for {@link Ext.JSON#encode}
8227  * @param {Mixed} o The variable to encode
8228  * @return {String} The JSON string
8229  * @member Ext
8230  * @method encode
8231  */
8232 Ext.encode = Ext.JSON.encode;
8233 /**
8234  * Shorthand for {@link Ext.JSON#decode}
8235  * @param {String} json The JSON string
8236  * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
8237  * @return {Object} The resulting object
8238  * @member Ext
8239  * @method decode
8240  */
8241 Ext.decode = Ext.JSON.decode;
8242
8243
8244 /**
8245  * @class Ext
8246
8247  The Ext namespace (global object) encapsulates all classes, singletons, and utility methods provided by Sencha's libraries.</p>
8248  Most user interface Components are at a lower level of nesting in the namespace, but many common utility functions are provided
8249  as direct properties of the Ext namespace.
8250
8251  Also many frequently used methods from other classes are provided as shortcuts within the Ext namespace.
8252  For example {@link Ext#getCmp Ext.getCmp} aliases {@link Ext.ComponentManager#get Ext.ComponentManager.get}.
8253
8254  Many applications are initiated with {@link Ext#onReady Ext.onReady} which is called once the DOM is ready.
8255  This ensures all scripts have been loaded, preventing dependency issues. For example
8256
8257      Ext.onReady(function(){
8258          new Ext.Component({
8259              renderTo: document.body,
8260              html: 'DOM ready!'
8261          });
8262      });
8263
8264 For more information about how to use the Ext classes, see
8265
8266 - <a href="http://www.sencha.com/learn/">The Learning Center</a>
8267 - <a href="http://www.sencha.com/learn/Ext_FAQ">The FAQ</a>
8268 - <a href="http://www.sencha.com/forum/">The forums</a>
8269
8270  * @singleton
8271  * @markdown
8272  */
8273 Ext.apply(Ext, {
8274     userAgent: navigator.userAgent.toLowerCase(),
8275     cache: {},
8276     idSeed: 1000,
8277     BLANK_IMAGE_URL : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
8278     isStrict: document.compatMode == "CSS1Compat",
8279     windowId: 'ext-window',
8280     documentId: 'ext-document',
8281
8282     /**
8283      * True when the document is fully initialized and ready for action
8284      * @type Boolean
8285      */
8286     isReady: false,
8287
8288     /**
8289      * True to automatically uncache orphaned Ext.core.Elements periodically (defaults to true)
8290      * @type Boolean
8291      */
8292     enableGarbageCollector: true,
8293
8294     /**
8295      * True to automatically purge event listeners during garbageCollection (defaults to true).
8296      * @type Boolean
8297      */
8298     enableListenerCollection: true,
8299
8300     /**
8301      * Generates unique ids. If the element already has an id, it is unchanged
8302      * @param {Mixed} el (optional) The element to generate an id for
8303      * @param {String} prefix (optional) Id prefix (defaults "ext-gen")
8304      * @return {String} The generated Id.
8305      */
8306     id: function(el, prefix) {
8307         el = Ext.getDom(el, true) || {};
8308         if (el === document) {
8309             el.id = this.documentId;
8310         }
8311         else if (el === window) {
8312             el.id = this.windowId;
8313         }
8314         if (!el.id) {
8315             el.id = (prefix || "ext-gen") + (++Ext.idSeed);
8316         }
8317         return el.id;
8318     },
8319
8320     /**
8321      * Returns the current document body as an {@link Ext.core.Element}.
8322      * @return Ext.core.Element The document body
8323      */
8324     getBody: function() {
8325         return Ext.get(document.body || false);
8326     },
8327
8328     /**
8329      * Returns the current document head as an {@link Ext.core.Element}.
8330      * @return Ext.core.Element The document head
8331      * @method
8332      */
8333     getHead: function() {
8334         var head;
8335
8336         return function() {
8337             if (head == undefined) {
8338                 head = Ext.get(document.getElementsByTagName("head")[0]);
8339             }
8340
8341             return head;
8342         };
8343     }(),
8344
8345     /**
8346      * Returns the current HTML document object as an {@link Ext.core.Element}.
8347      * @return Ext.core.Element The document
8348      */
8349     getDoc: function() {
8350         return Ext.get(document);
8351     },
8352
8353     /**
8354      * This is shorthand reference to {@link Ext.ComponentManager#get}.
8355      * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#id id}
8356      * @param {String} id The component {@link Ext.Component#id id}
8357      * @return Ext.Component The Component, <tt>undefined</tt> if not found, or <tt>null</tt> if a
8358      * Class was found.
8359     */
8360     getCmp: function(id) {
8361         return Ext.ComponentManager.get(id);
8362     },
8363
8364     /**
8365      * Returns the current orientation of the mobile device
8366      * @return {String} Either 'portrait' or 'landscape'
8367      */
8368     getOrientation: function() {
8369         return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape';
8370     },
8371
8372     /**
8373      * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the
8374      * DOM (if applicable) and calling their destroy functions (if available).  This method is primarily
8375      * intended for arguments of type {@link Ext.core.Element} and {@link Ext.Component}, but any subclass of
8376      * {@link Ext.util.Observable} can be passed in.  Any number of elements and/or components can be
8377      * passed into this function in a single call as separate arguments.
8378      * @param {Mixed} arg1 An {@link Ext.core.Element}, {@link Ext.Component}, or an Array of either of these to destroy
8379      * @param {Mixed} arg2 (optional)
8380      * @param {Mixed} etc... (optional)
8381      */
8382     destroy: function() {
8383         var ln = arguments.length,
8384         i, arg;
8385
8386         for (i = 0; i < ln; i++) {
8387             arg = arguments[i];
8388             if (arg) {
8389                 if (Ext.isArray(arg)) {
8390                     this.destroy.apply(this, arg);
8391                 }
8392                 else if (Ext.isFunction(arg.destroy)) {
8393                     arg.destroy();
8394                 }
8395                 else if (arg.dom) {
8396                     arg.remove();
8397                 }
8398             }
8399         }
8400     },
8401
8402     /**
8403      * Execute a callback function in a particular scope. If no function is passed the call is ignored.
8404      * @param {Function} callback The callback to execute
8405      * @param {Object} scope (optional) The scope to execute in
8406      * @param {Array} args (optional) The arguments to pass to the function
8407      * @param {Number} delay (optional) Pass a number to delay the call by a number of milliseconds.
8408      */
8409     callback: function(callback, scope, args, delay){
8410         if(Ext.isFunction(callback)){
8411             args = args || [];
8412             scope = scope || window;
8413             if (delay) {
8414                 Ext.defer(callback, delay, scope, args);
8415             } else {
8416                 callback.apply(scope, args);
8417             }
8418         }
8419     },
8420
8421     /**
8422      * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
8423      * @param {String} value The string to encode
8424      * @return {String} The encoded text
8425      */
8426     htmlEncode : function(value) {
8427         return Ext.String.htmlEncode(value);
8428     },
8429
8430     /**
8431      * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
8432      * @param {String} value The string to decode
8433      * @return {String} The decoded text
8434      */
8435     htmlDecode : function(value) {
8436          return Ext.String.htmlDecode(value);
8437     },
8438
8439     /**
8440      * Appends content to the query string of a URL, handling logic for whether to place
8441      * a question mark or ampersand.
8442      * @param {String} url The URL to append to.
8443      * @param {String} s The content to append to the URL.
8444      * @return (String) The resulting URL
8445      */
8446     urlAppend : function(url, s) {
8447         if (!Ext.isEmpty(s)) {
8448             return url + (url.indexOf('?') === -1 ? '?' : '&') + s;
8449         }
8450         return url;
8451     }
8452 });
8453
8454
8455 Ext.ns = Ext.namespace;
8456
8457 // for old browsers
8458 window.undefined = window.undefined;
8459
8460 /**
8461  * @class Ext
8462  * Ext core utilities and functions.
8463  * @singleton
8464  */
8465 (function(){
8466     var check = function(regex){
8467             return regex.test(Ext.userAgent);
8468         },
8469         docMode = document.documentMode,
8470         isOpera = check(/opera/),
8471         isOpera10_5 = isOpera && check(/version\/10\.5/),
8472         isChrome = check(/\bchrome\b/),
8473         isWebKit = check(/webkit/),
8474         isSafari = !isChrome && check(/safari/),
8475         isSafari2 = isSafari && check(/applewebkit\/4/), // unique to Safari 2
8476         isSafari3 = isSafari && check(/version\/3/),
8477         isSafari4 = isSafari && check(/version\/4/),
8478         isIE = !isOpera && check(/msie/),
8479         isIE7 = isIE && (check(/msie 7/) || docMode == 7),
8480         isIE8 = isIE && (check(/msie 8/) && docMode != 7 && docMode != 9 || docMode == 8),
8481         isIE9 = isIE && (check(/msie 9/) && docMode != 7 && docMode != 8 || docMode == 9),
8482         isIE6 = isIE && check(/msie 6/),
8483         isGecko = !isWebKit && check(/gecko/),
8484         isGecko3 = isGecko && check(/rv:1\.9/),
8485         isGecko4 = isGecko && check(/rv:2\.0/),
8486         isFF3_0 = isGecko3 && check(/rv:1\.9\.0/),
8487         isFF3_5 = isGecko3 && check(/rv:1\.9\.1/),
8488         isFF3_6 = isGecko3 && check(/rv:1\.9\.2/),
8489         isWindows = check(/windows|win32/),
8490         isMac = check(/macintosh|mac os x/),
8491         isLinux = check(/linux/),
8492         scrollWidth = null,
8493         webKitVersion = isWebKit && (/webkit\/(\d+\.\d+)/.exec(Ext.userAgent));
8494
8495     // remove css image flicker
8496     try {
8497         document.execCommand("BackgroundImageCache", false, true);
8498     } catch(e) {}
8499
8500     Ext.setVersion('extjs', '4.0.1');
8501     Ext.apply(Ext, {
8502         /**
8503          * URL to a blank file used by Ext when in secure mode for iframe src and onReady src to prevent
8504          * the IE insecure content warning (<tt>'about:blank'</tt>, except for IE in secure mode, which is <tt>'javascript:""'</tt>).
8505          * @type String
8506          */
8507         SSL_SECURE_URL : Ext.isSecure && isIE ? 'javascript:""' : 'about:blank',
8508
8509         /**
8510          * True if the {@link Ext.fx.Anim} Class is available
8511          * @type Boolean
8512          * @property enableFx
8513          */
8514
8515         /**
8516          * True to scope the reset CSS to be just applied to Ext components. Note that this wraps root containers
8517          * with an additional element. Also remember that when you turn on this option, you have to use ext-all-scoped {
8518          * unless you use the bootstrap.js to load your javascript, in which case it will be handled for you.
8519          * @type Boolean
8520          */
8521         scopeResetCSS : Ext.buildSettings.scopeResetCSS,
8522
8523         /**
8524          * EXPERIMENTAL - True to cascade listener removal to child elements when an element is removed.
8525          * Currently not optimized for performance.
8526          * @type Boolean
8527          */
8528         enableNestedListenerRemoval : false,
8529
8530         /**
8531          * Indicates whether to use native browser parsing for JSON methods.
8532          * This option is ignored if the browser does not support native JSON methods.
8533          * <b>Note: Native JSON methods will not work with objects that have functions.
8534          * Also, property names must be quoted, otherwise the data will not parse.</b> (Defaults to false)
8535          * @type Boolean
8536          */
8537         USE_NATIVE_JSON : false,
8538
8539         /**
8540          * Return the dom node for the passed String (id), dom node, or Ext.core.Element.
8541          * Optional 'strict' flag is needed for IE since it can return 'name' and
8542          * 'id' elements by using getElementById.
8543          * Here are some examples:
8544          * <pre><code>
8545 // gets dom node based on id
8546 var elDom = Ext.getDom('elId');
8547 // gets dom node based on the dom node
8548 var elDom1 = Ext.getDom(elDom);
8549
8550 // If we don&#39;t know if we are working with an
8551 // Ext.core.Element or a dom node use Ext.getDom
8552 function(el){
8553     var dom = Ext.getDom(el);
8554     // do something with the dom node
8555 }
8556          * </code></pre>
8557          * <b>Note</b>: the dom node to be found actually needs to exist (be rendered, etc)
8558          * when this method is called to be successful.
8559          * @param {Mixed} el
8560          * @return HTMLElement
8561          */
8562         getDom : function(el, strict) {
8563             if (!el || !document) {
8564                 return null;
8565             }
8566             if (el.dom) {
8567                 return el.dom;
8568             } else {
8569                 if (typeof el == 'string') {
8570                     var e = document.getElementById(el);
8571                     // IE returns elements with the 'name' and 'id' attribute.
8572                     // we do a strict check to return the element with only the id attribute
8573                     if (e && isIE && strict) {
8574                         if (el == e.getAttribute('id')) {
8575                             return e;
8576                         } else {
8577                             return null;
8578                         }
8579                     }
8580                     return e;
8581                 } else {
8582                     return el;
8583                 }
8584             }
8585         },
8586
8587         /**
8588          * Removes a DOM node from the document.
8589          * <p>Removes this element from the document, removes all DOM event listeners, and deletes the cache reference.
8590          * All DOM event listeners are removed from this element. If {@link Ext#enableNestedListenerRemoval Ext.enableNestedListenerRemoval} is
8591          * <code>true</code>, then DOM event listeners are also removed from all child nodes. The body node
8592          * will be ignored if passed in.</p>
8593          * @param {HTMLElement} node The node to remove
8594          * @method
8595          */
8596         removeNode : isIE6 || isIE7 ? function() {
8597             var d;
8598             return function(n){
8599                 if(n && n.tagName != 'BODY'){
8600                     (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
8601                     d = d || document.createElement('div');
8602                     d.appendChild(n);
8603                     d.innerHTML = '';
8604                     delete Ext.cache[n.id];
8605                 }
8606             };
8607         }() : function(n) {
8608             if (n && n.parentNode && n.tagName != 'BODY') {
8609                 (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
8610                 n.parentNode.removeChild(n);
8611                 delete Ext.cache[n.id];
8612             }
8613         },
8614
8615         /**
8616          * True if the detected browser is Opera.
8617          * @type Boolean
8618          */
8619         isOpera : isOpera,
8620
8621         /**
8622          * True if the detected browser is Opera 10.5x.
8623          * @type Boolean
8624          */
8625         isOpera10_5 : isOpera10_5,
8626
8627         /**
8628          * True if the detected browser uses WebKit.
8629          * @type Boolean
8630          */
8631         isWebKit : isWebKit,
8632
8633         /**
8634          * True if the detected browser is Chrome.
8635          * @type Boolean
8636          */
8637         isChrome : isChrome,
8638
8639         /**
8640          * True if the detected browser is Safari.
8641          * @type Boolean
8642          */
8643         isSafari : isSafari,
8644
8645         /**
8646          * True if the detected browser is Safari 3.x.
8647          * @type Boolean
8648          */
8649         isSafari3 : isSafari3,
8650
8651         /**
8652          * True if the detected browser is Safari 4.x.
8653          * @type Boolean
8654          */
8655         isSafari4 : isSafari4,
8656
8657         /**
8658          * True if the detected browser is Safari 2.x.
8659          * @type Boolean
8660          */
8661         isSafari2 : isSafari2,
8662
8663         /**
8664          * True if the detected browser is Internet Explorer.
8665          * @type Boolean
8666          */
8667         isIE : isIE,
8668
8669         /**
8670          * True if the detected browser is Internet Explorer 6.x.
8671          * @type Boolean
8672          */
8673         isIE6 : isIE6,
8674
8675         /**
8676          * True if the detected browser is Internet Explorer 7.x.
8677          * @type Boolean
8678          */
8679         isIE7 : isIE7,
8680
8681         /**
8682          * True if the detected browser is Internet Explorer 8.x.
8683          * @type Boolean
8684          */
8685         isIE8 : isIE8,
8686
8687         /**
8688          * True if the detected browser is Internet Explorer 9.x.
8689          * @type Boolean
8690          */
8691         isIE9 : isIE9,
8692
8693         /**
8694          * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox).
8695          * @type Boolean
8696          */
8697         isGecko : isGecko,
8698
8699         /**
8700          * True if the detected browser uses a Gecko 1.9+ layout engine (e.g. Firefox 3.x).
8701          * @type Boolean
8702          */
8703         isGecko3 : isGecko3,
8704
8705         /**
8706          * True if the detected browser uses a Gecko 2.0+ layout engine (e.g. Firefox 4.x).
8707          * @type Boolean
8708          */
8709         isGecko4 : isGecko4,
8710
8711         /**
8712          * True if the detected browser uses FireFox 3.0
8713          * @type Boolean
8714          */
8715
8716         isFF3_0 : isFF3_0,
8717         /**
8718          * True if the detected browser uses FireFox 3.5
8719          * @type Boolean
8720          */
8721
8722         isFF3_5 : isFF3_5,
8723         /**
8724          * True if the detected browser uses FireFox 3.6
8725          * @type Boolean
8726          */
8727         isFF3_6 : isFF3_6,
8728
8729         /**
8730          * True if the detected platform is Linux.
8731          * @type Boolean
8732          */
8733         isLinux : isLinux,
8734
8735         /**
8736          * True if the detected platform is Windows.
8737          * @type Boolean
8738          */
8739         isWindows : isWindows,
8740
8741         /**
8742          * True if the detected platform is Mac OS.
8743          * @type Boolean
8744          */
8745         isMac : isMac,
8746
8747         /**
8748          * The current version of WebKit (-1 if the browser does not use WebKit).
8749          * @type Float
8750          */
8751         webKitVersion: webKitVersion ? parseFloat(webKitVersion[1]) : -1,
8752
8753         /**
8754          * URL to a 1x1 transparent gif image used by Ext to create inline icons with CSS background images.
8755          * In older versions of IE, this defaults to "http://sencha.com/s.gif" and you should change this to a URL on your server.
8756          * For other browsers it uses an inline data URL.
8757          * @type String
8758          */
8759         BLANK_IMAGE_URL : (isIE6 || isIE7) ? 'http:/' + '/www.sencha.com/s.gif' : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
8760
8761         /**
8762          * <p>Utility method for returning a default value if the passed value is empty.</p>
8763          * <p>The value is deemed to be empty if it is<div class="mdetail-params"><ul>
8764          * <li>null</li>
8765          * <li>undefined</li>
8766          * <li>an empty array</li>
8767          * <li>a zero length string (Unless the <tt>allowBlank</tt> parameter is <tt>true</tt>)</li>
8768          * </ul></div>
8769          * @param {Mixed} value The value to test
8770          * @param {Mixed} defaultValue The value to return if the original value is empty
8771          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
8772          * @return {Mixed} value, if non-empty, else defaultValue
8773          * @deprecated 4.0.0 Use {Ext#valueFrom} instead
8774          */
8775         value : function(v, defaultValue, allowBlank){
8776             return Ext.isEmpty(v, allowBlank) ? defaultValue : v;
8777         },
8778
8779         /**
8780          * Escapes the passed string for use in a regular expression
8781          * @param {String} str
8782          * @return {String}
8783          * @deprecated 4.0.0 Use {@link Ext.String#escapeRegex} instead
8784          */
8785         escapeRe : function(s) {
8786             return s.replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1");
8787         },
8788
8789         /**
8790          * Applies event listeners to elements by selectors when the document is ready.
8791          * The event name is specified with an <tt>&#64;</tt> suffix.
8792          * <pre><code>
8793 Ext.addBehaviors({
8794     // add a listener for click on all anchors in element with id foo
8795     '#foo a&#64;click' : function(e, t){
8796         // do something
8797     },
8798
8799     // add the same listener to multiple selectors (separated by comma BEFORE the &#64;)
8800     '#foo a, #bar span.some-class&#64;mouseover' : function(){
8801         // do something
8802     }
8803 });
8804          * </code></pre>
8805          * @param {Object} obj The list of behaviors to apply
8806          */
8807         addBehaviors : function(o){
8808             if(!Ext.isReady){
8809                 Ext.onReady(function(){
8810                     Ext.addBehaviors(o);
8811                 });
8812             } else {
8813                 var cache = {}, // simple cache for applying multiple behaviors to same selector does query multiple times
8814                     parts,
8815                     b,
8816                     s;
8817                 for (b in o) {
8818                     if ((parts = b.split('@'))[1]) { // for Object prototype breakers
8819                         s = parts[0];
8820                         if(!cache[s]){
8821                             cache[s] = Ext.select(s);
8822                         }
8823                         cache[s].on(parts[1], o[b]);
8824                     }
8825                 }
8826                 cache = null;
8827             }
8828         },
8829
8830         /**
8831          * Utility method for getting the width of the browser scrollbar. This can differ depending on
8832          * operating system settings, such as the theme or font size.
8833          * @param {Boolean} force (optional) true to force a recalculation of the value.
8834          * @return {Number} The width of the scrollbar.
8835          */
8836         getScrollBarWidth: function(force){
8837             if(!Ext.isReady){
8838                 return 0;
8839             }
8840
8841             if(force === true || scrollWidth === null){
8842                 // BrowserBug: IE9
8843                 // When IE9 positions an element offscreen via offsets, the offsetWidth is
8844                 // inaccurately reported. For IE9 only, we render on screen before removing.
8845                 var cssClass = Ext.isIE9 ? '' : Ext.baseCSSPrefix + 'hide-offsets';
8846                     // Append our div, do our calculation and then remove it
8847                 var div = Ext.getBody().createChild('<div class="' + cssClass + '" style="width:100px;height:50px;overflow:hidden;"><div style="height:200px;"></div></div>'),
8848                     child = div.child('div', true);
8849                 var w1 = child.offsetWidth;
8850                 div.setStyle('overflow', (Ext.isWebKit || Ext.isGecko) ? 'auto' : 'scroll');
8851                 var w2 = child.offsetWidth;
8852                 div.remove();
8853                 // Need to add 2 to ensure we leave enough space
8854                 scrollWidth = w1 - w2 + 2;
8855             }
8856             return scrollWidth;
8857         },
8858
8859         /**
8860          * Copies a set of named properties fom the source object to the destination object.
8861          * <p>example:<pre><code>
8862 ImageComponent = Ext.extend(Ext.Component, {
8863     initComponent: function() {
8864         this.autoEl = { tag: 'img' };
8865         MyComponent.superclass.initComponent.apply(this, arguments);
8866         this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height');
8867     }
8868 });
8869          * </code></pre>
8870          * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
8871          * @param {Object} dest The destination object.
8872          * @param {Object} source The source object.
8873          * @param {Array/String} names Either an Array of property names, or a comma-delimited list
8874          * of property names to copy.
8875          * @param {Boolean} usePrototypeKeys (Optional) Defaults to false. Pass true to copy keys off of the prototype as well as the instance.
8876          * @return {Object} The modified object.
8877         */
8878         copyTo : function(dest, source, names, usePrototypeKeys){
8879             if(typeof names == 'string'){
8880                 names = names.split(/[,;\s]/);
8881             }
8882             Ext.each(names, function(name){
8883                 if(usePrototypeKeys || source.hasOwnProperty(name)){
8884                     dest[name] = source[name];
8885                 }
8886             }, this);
8887             return dest;
8888         },
8889
8890         /**
8891          * Attempts to destroy and then remove a set of named properties of the passed object.
8892          * @param {Object} o The object (most likely a Component) who's properties you wish to destroy.
8893          * @param {Mixed} arg1 The name of the property to destroy and remove from the object.
8894          * @param {Mixed} etc... More property names to destroy and remove.
8895          */
8896         destroyMembers : function(o, arg1, arg2, etc){
8897             for (var i = 1, a = arguments, len = a.length; i < len; i++) {
8898                 Ext.destroy(o[a[i]]);
8899                 delete o[a[i]];
8900             }
8901         },
8902
8903         /**
8904          * Logs a message. If a console is present it will be used. On Opera, the method
8905          * "opera.postError" is called. In other cases, the message is logged to an array
8906          * "Ext.log.out". An attached debugger can watch this array and view the log. The
8907          * log buffer is limited to a maximum of "Ext.log.max" entries (defaults to 100).
8908          *
8909          * If additional parameters are passed, they are joined and appended to the message.
8910          * 
8911          * This method does nothing in a release build.
8912          *
8913          * @param {String|Object} message The message to log or an options object with any
8914          * of the following properties:
8915          *
8916          *  - `msg`: The message to log (required).
8917          *  - `level`: One of: "error", "warn", "info" or "log" (the default is "log").
8918          *  - `dump`: An object to dump to the log as part of the message.
8919          *  - `stack`: True to include a stack trace in the log.
8920          * @markdown
8921          */
8922         log : function (message) {
8923             var options, dump,
8924                 con = Ext.global.console,
8925                 log = Ext.log,
8926                 level = 'log',
8927                 stack,
8928                 members,
8929                 member;
8930
8931             if (!Ext.isString(message)) {
8932                 options = message;
8933                 message = options.msg || '';
8934                 level = options.level || level;
8935                 dump = options.dump;
8936                 stack = options.stack;
8937
8938                 if (dump && !(con && con.dir)) {
8939                     members = [];
8940
8941                     // Cannot use Ext.encode since it can recurse endlessly (if we're lucky)
8942                     // ...and the data could be prettier!
8943                     Ext.Object.each(dump, function (name, value) {
8944                         if (typeof(value) === "function") {
8945                             return;
8946                         }
8947
8948                         if (!Ext.isDefined(value) || value === null ||
8949                                 Ext.isDate(value) ||
8950                                 Ext.isString(value) || (typeof(value) == "number") ||
8951                                 Ext.isBoolean(value)) {
8952                             member = Ext.encode(value);
8953                         } else if (Ext.isArray(value)) {
8954                             member = '[ ]';
8955                         } else if (Ext.isObject(value)) {
8956                             member = '{ }';
8957                         } else {
8958                             member = 'undefined';
8959                         }
8960                         members.push(Ext.encode(name) + ': ' + member);
8961                     });
8962
8963                     if (members.length) {
8964                         message += ' \nData: {\n  ' + members.join(',\n  ') + '\n}';
8965                     }
8966                     dump = null;
8967                 }
8968             }
8969
8970             if (arguments.length > 1) {
8971                 message += Array.prototype.slice.call(arguments, 1).join('');
8972             }
8973
8974             // Not obvious, but 'console' comes and goes when Firebug is turned on/off, so
8975             // an early test may fail either direction if Firebug is toggled.
8976             //
8977             if (con) { // if (Firebug-like console)
8978                 if (con[level]) {
8979                     con[level](message);
8980                 } else {
8981                     con.log(message);
8982                 }
8983
8984                 if (dump) {
8985                     con.dir(dump);
8986                 }
8987
8988                 if (stack && con.trace) {
8989                     // Firebug's console.error() includes a trace already...
8990                     if (!con.firebug || level != 'error') {
8991                         con.trace();
8992                     }
8993                 }
8994             } else {
8995                 // w/o console, all messages are equal, so munge the level into the message:
8996                 if (level != 'log') {
8997                     message = level.toUpperCase() + ': ' + message;
8998                 }
8999
9000                 if (Ext.isOpera) {
9001                     opera.postError(message);
9002                 } else {
9003                     var out = log.out || (log.out = []),
9004                         max = log.max || (log.max = 100);
9005
9006                     if (out.length >= max) {
9007                         // this formula allows out.max to change (via debugger), where the
9008                         // more obvious "max/4" would not quite be the same
9009                         out.splice(0, out.length - 3 * Math.floor(max / 4)); // keep newest 75%
9010                     }
9011
9012                     out.push(message);
9013                 }
9014             }
9015
9016             // Mostly informational, but the Ext.Error notifier uses them:
9017             var counters = log.counters ||
9018                           (log.counters = { error: 0, warn: 0, info: 0, log: 0 });
9019
9020             ++counters[level];
9021         },
9022
9023         /**
9024          * Partitions the set into two sets: a true set and a false set.
9025          * Example:
9026          * Example2:
9027          * <pre><code>
9028 // Example 1:
9029 Ext.partition([true, false, true, true, false]); // [[true, true, true], [false, false]]
9030
9031 // Example 2:
9032 Ext.partition(
9033     Ext.query("p"),
9034     function(val){
9035         return val.className == "class1"
9036     }
9037 );
9038 // true are those paragraph elements with a className of "class1",
9039 // false set are those that do not have that className.
9040          * </code></pre>
9041          * @param {Array|NodeList} arr The array to partition
9042          * @param {Function} truth (optional) a function to determine truth.  If this is omitted the element
9043          *                   itself must be able to be evaluated for its truthfulness.
9044          * @return {Array} [true<Array>,false<Array>]
9045          * @deprecated 4.0.0 Will be removed in the next major version
9046          */
9047         partition : function(arr, truth){
9048             var ret = [[],[]];
9049             Ext.each(arr, function(v, i, a) {
9050                 ret[ (truth && truth(v, i, a)) || (!truth && v) ? 0 : 1].push(v);
9051             });
9052             return ret;
9053         },
9054
9055         /**
9056          * Invokes a method on each item in an Array.
9057          * <pre><code>
9058 // Example:
9059 Ext.invoke(Ext.query("p"), "getAttribute", "id");
9060 // [el1.getAttribute("id"), el2.getAttribute("id"), ..., elN.getAttribute("id")]
9061          * </code></pre>
9062          * @param {Array|NodeList} arr The Array of items to invoke the method on.
9063          * @param {String} methodName The method name to invoke.
9064          * @param {...*} args Arguments to send into the method invocation.
9065          * @return {Array} The results of invoking the method on each item in the array.
9066          * @deprecated 4.0.0 Will be removed in the next major version
9067          */
9068         invoke : function(arr, methodName){
9069             var ret = [],
9070                 args = Array.prototype.slice.call(arguments, 2);
9071             Ext.each(arr, function(v,i) {
9072                 if (v && typeof v[methodName] == 'function') {
9073                     ret.push(v[methodName].apply(v, args));
9074                 } else {
9075                     ret.push(undefined);
9076                 }
9077             });
9078             return ret;
9079         },
9080
9081         /**
9082          * <p>Zips N sets together.</p>
9083          * <pre><code>
9084 // Example 1:
9085 Ext.zip([1,2,3],[4,5,6]); // [[1,4],[2,5],[3,6]]
9086 // Example 2:
9087 Ext.zip(
9088     [ "+", "-", "+"],
9089     [  12,  10,  22],
9090     [  43,  15,  96],
9091     function(a, b, c){
9092         return "$" + a + "" + b + "." + c
9093     }
9094 ); // ["$+12.43", "$-10.15", "$+22.96"]
9095          * </code></pre>
9096          * @param {Arrays|NodeLists} arr This argument may be repeated. Array(s) to contribute values.
9097          * @param {Function} zipper (optional) The last item in the argument list. This will drive how the items are zipped together.
9098          * @return {Array} The zipped set.
9099          * @deprecated 4.0.0 Will be removed in the next major version
9100          */
9101         zip : function(){
9102             var parts = Ext.partition(arguments, function( val ){ return typeof val != 'function'; }),
9103                 arrs = parts[0],
9104                 fn = parts[1][0],
9105                 len = Ext.max(Ext.pluck(arrs, "length")),
9106                 ret = [];
9107
9108             for (var i = 0; i < len; i++) {
9109                 ret[i] = [];
9110                 if(fn){
9111                     ret[i] = fn.apply(fn, Ext.pluck(arrs, i));
9112                 }else{
9113                     for (var j = 0, aLen = arrs.length; j < aLen; j++){
9114                         ret[i].push( arrs[j][i] );
9115                     }
9116                 }
9117             }
9118             return ret;
9119         },
9120
9121         /**
9122          * Turns an array into a sentence, joined by a specified connector - e.g.:
9123          * Ext.toSentence(['Adama', 'Tigh', 'Roslin']); //'Adama, Tigh and Roslin'
9124          * Ext.toSentence(['Adama', 'Tigh', 'Roslin'], 'or'); //'Adama, Tigh or Roslin'
9125          * @param {Array} items The array to create a sentence from
9126          * @param {String} connector The string to use to connect the last two words. Usually 'and' or 'or' - defaults to 'and'.
9127          * @return {String} The sentence string
9128          * @deprecated 4.0.0 Will be removed in the next major version
9129          */
9130         toSentence: function(items, connector) {
9131             var length = items.length;
9132
9133             if (length <= 1) {
9134                 return items[0];
9135             } else {
9136                 var head = items.slice(0, length - 1),
9137                     tail = items[length - 1];
9138
9139                 return Ext.util.Format.format("{0} {1} {2}", head.join(", "), connector || 'and', tail);
9140             }
9141         },
9142
9143         /**
9144          * By default, Ext intelligently decides whether floating elements should be shimmed. If you are using flash,
9145          * you may want to set this to true.
9146          * @type Boolean
9147          */
9148         useShims: isIE6
9149     });
9150 })();
9151
9152 /**
9153  * TBD
9154  * @param {Object} config
9155  * @method
9156  */
9157 Ext.application = function(config) {
9158     Ext.require('Ext.app.Application');
9159
9160     Ext.onReady(function() {
9161         Ext.create('Ext.app.Application', config);
9162     });
9163 };
9164
9165 /**
9166  * @class Ext.util.Format
9167
9168 This class is a centralized place for formatting functions inside the library. It includes
9169 functions to format various different types of data, such as text, dates and numeric values.
9170
9171 __Localization__
9172 This class contains several options for localization. These can be set once the library has loaded,
9173 all calls to the functions from that point will use the locale settings that were specified.
9174 Options include:
9175 - thousandSeparator
9176 - decimalSeparator
9177 - currenyPrecision
9178 - currencySign
9179 - currencyAtEnd
9180 This class also uses the default date format defined here: {@link Ext.date#defaultFormat}.
9181
9182 __Using with renderers__
9183 There are two helper functions that return a new function that can be used in conjunction with 
9184 grid renderers:
9185
9186     columns: [{
9187         dataIndex: 'date',
9188         renderer: Ext.util.Format.dateRenderer('Y-m-d')
9189     }, {
9190         dataIndex: 'time',
9191         renderer: Ext.util.Format.numberRenderer('0.000')
9192     }]
9193     
9194 Functions that only take a single argument can also be passed directly:
9195     columns: [{
9196         dataIndex: 'cost',
9197         renderer: Ext.util.Format.usMoney
9198     }, {
9199         dataIndex: 'productCode',
9200         renderer: Ext.util.Format.uppercase
9201     }]
9202     
9203 __Using with XTemplates__
9204 XTemplates can also directly use Ext.util.Format functions:
9205
9206     new Ext.XTemplate([
9207         'Date: {startDate:date("Y-m-d")}',
9208         'Cost: {cost:usMoney}'
9209     ]);
9210
9211  * @markdown
9212  * @singleton
9213  */
9214 (function() {
9215     Ext.ns('Ext.util');
9216
9217     Ext.util.Format = {};
9218     var UtilFormat     = Ext.util.Format,
9219         stripTagsRE    = /<\/?[^>]+>/gi,
9220         stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
9221         nl2brRe        = /\r?\n/g,
9222
9223         // A RegExp to remove from a number format string, all characters except digits and '.'
9224         formatCleanRe  = /[^\d\.]/g,
9225
9226         // A RegExp to remove from a number format string, all characters except digits and the local decimal separator.
9227         // Created on first use. The local decimal separator character must be initialized for this to be created.
9228         I18NFormatCleanRe;
9229
9230     Ext.apply(UtilFormat, {
9231         /**
9232          * @type String
9233          * @property thousandSeparator
9234          * <p>The character that the {@link #number} function uses as a thousand separator.</p>
9235          * <p>This defaults to <code>,</code>, but may be overridden in a locale file.</p>
9236          */
9237         thousandSeparator: ',',
9238
9239         /**
9240          * @type String
9241          * @property decimalSeparator
9242          * <p>The character that the {@link #number} function uses as a decimal point.</p>
9243          * <p>This defaults to <code>.</code>, but may be overridden in a locale file.</p>
9244          */
9245         decimalSeparator: '.',
9246
9247         /**
9248          * @type Number
9249          * @property currencyPrecision
9250          * <p>The number of decimal places that the {@link #currency} function displays.</p>
9251          * <p>This defaults to <code>2</code>, but may be overridden in a locale file.</p>
9252          */
9253         currencyPrecision: 2,
9254
9255         /**
9256          * @type String
9257          * @property currencySign
9258          * <p>The currency sign that the {@link #currency} function displays.</p>
9259          * <p>This defaults to <code>$</code>, but may be overridden in a locale file.</p>
9260          */
9261         currencySign: '$',
9262
9263         /**
9264          * @type Boolean
9265          * @property currencyAtEnd
9266          * <p>This may be set to <code>true</code> to make the {@link #currency} function
9267          * append the currency sign to the formatted value.</p>
9268          * <p>This defaults to <code>false</code>, but may be overridden in a locale file.</p>
9269          */
9270         currencyAtEnd: false,
9271
9272         /**
9273          * Checks a reference and converts it to empty string if it is undefined
9274          * @param {Mixed} value Reference to check
9275          * @return {Mixed} Empty string if converted, otherwise the original value
9276          */
9277         undef : function(value) {
9278             return value !== undefined ? value : "";
9279         },
9280
9281         /**
9282          * Checks a reference and converts it to the default value if it's empty
9283          * @param {Mixed} value Reference to check
9284          * @param {String} defaultValue The value to insert of it's undefined (defaults to "")
9285          * @return {String}
9286          */
9287         defaultValue : function(value, defaultValue) {
9288             return value !== undefined && value !== '' ? value : defaultValue;
9289         },
9290
9291         /**
9292          * Returns a substring from within an original string
9293          * @param {String} value The original text
9294          * @param {Number} start The start index of the substring
9295          * @param {Number} length The length of the substring
9296          * @return {String} The substring
9297          */
9298         substr : function(value, start, length) {
9299             return String(value).substr(start, length);
9300         },
9301
9302         /**
9303          * Converts a string to all lower case letters
9304          * @param {String} value The text to convert
9305          * @return {String} The converted text
9306          */
9307         lowercase : function(value) {
9308             return String(value).toLowerCase();
9309         },
9310
9311         /**
9312          * Converts a string to all upper case letters
9313          * @param {String} value The text to convert
9314          * @return {String} The converted text
9315          */
9316         uppercase : function(value) {
9317             return String(value).toUpperCase();
9318         },
9319
9320         /**
9321          * Format a number as US currency
9322          * @param {Number/String} value The numeric value to format
9323          * @return {String} The formatted currency string
9324          */
9325         usMoney : function(v) {
9326             return UtilFormat.currency(v, '$', 2);
9327         },
9328
9329         /**
9330          * Format a number as a currency
9331          * @param {Number/String} value The numeric value to format
9332          * @param {String} sign The currency sign to use (defaults to {@link #currencySign})
9333          * @param {Number} decimals The number of decimals to use for the currency (defaults to {@link #currencyPrecision})
9334          * @param {Boolean} end True if the currency sign should be at the end of the string (defaults to {@link #currencyAtEnd})
9335          * @return {String} The formatted currency string
9336          */
9337         currency: function(v, currencySign, decimals, end) {
9338             var negativeSign = '',
9339                 format = ",0",
9340                 i = 0;
9341             v = v - 0;
9342             if (v < 0) {
9343                 v = -v;
9344                 negativeSign = '-';
9345             }
9346             decimals = decimals || UtilFormat.currencyPrecision;
9347             format += format + (decimals > 0 ? '.' : '');
9348             for (; i < decimals; i++) {
9349                 format += '0';
9350             }
9351             v = UtilFormat.number(v, format); 
9352             if ((end || UtilFormat.currencyAtEnd) === true) {
9353                 return Ext.String.format("{0}{1}{2}", negativeSign, v, currencySign || UtilFormat.currencySign);
9354             } else {
9355                 return Ext.String.format("{0}{1}{2}", negativeSign, currencySign || UtilFormat.currencySign, v);
9356             }
9357         },
9358
9359         /**
9360          * Formats the passed date using the specified format pattern.
9361          * @param {String/Date} value The value to format. If a string is passed, it is converted to a Date by the Javascript
9362          * Date object's <a href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method.
9363          * @param {String} format (Optional) Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
9364          * @return {String} The formatted date string.
9365          */
9366         date: function(v, format) {
9367             if (!v) {
9368                 return "";
9369             }
9370             if (!Ext.isDate(v)) {
9371                 v = new Date(Date.parse(v));
9372             }
9373             return Ext.Date.dateFormat(v, format || Ext.Date.defaultFormat);
9374         },
9375
9376         /**
9377          * Returns a date rendering function that can be reused to apply a date format multiple times efficiently
9378          * @param {String} format Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
9379          * @return {Function} The date formatting function
9380          */
9381         dateRenderer : function(format) {
9382             return function(v) {
9383                 return UtilFormat.date(v, format);
9384             };
9385         },
9386
9387         /**
9388          * Strips all HTML tags
9389          * @param {Mixed} value The text from which to strip tags
9390          * @return {String} The stripped text
9391          */
9392         stripTags : function(v) {
9393             return !v ? v : String(v).replace(stripTagsRE, "");
9394         },
9395
9396         /**
9397          * Strips all script tags
9398          * @param {Mixed} value The text from which to strip script tags
9399          * @return {String} The stripped text
9400          */
9401         stripScripts : function(v) {
9402             return !v ? v : String(v).replace(stripScriptsRe, "");
9403         },
9404
9405         /**
9406          * Simple format for a file size (xxx bytes, xxx KB, xxx MB)
9407          * @param {Number/String} size The numeric value to format
9408          * @return {String} The formatted file size
9409          */
9410         fileSize : function(size) {
9411             if (size < 1024) {
9412                 return size + " bytes";
9413             } else if (size < 1048576) {
9414                 return (Math.round(((size*10) / 1024))/10) + " KB";
9415             } else {
9416                 return (Math.round(((size*10) / 1048576))/10) + " MB";
9417             }
9418         },
9419
9420         /**
9421          * It does simple math for use in a template, for example:<pre><code>
9422          * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
9423          * </code></pre>
9424          * @return {Function} A function that operates on the passed value.
9425          * @method
9426          */
9427         math : function(){
9428             var fns = {};
9429
9430             return function(v, a){
9431                 if (!fns[a]) {
9432                     fns[a] = Ext.functionFactory('v', 'return v ' + a + ';');
9433                 }
9434                 return fns[a](v);
9435             };
9436         }(),
9437
9438         /**
9439          * Rounds the passed number to the required decimal precision.
9440          * @param {Number/String} value The numeric value to round.
9441          * @param {Number} precision The number of decimal places to which to round the first parameter's value.
9442          * @return {Number} The rounded value.
9443          */
9444         round : function(value, precision) {
9445             var result = Number(value);
9446             if (typeof precision == 'number') {
9447                 precision = Math.pow(10, precision);
9448                 result = Math.round(value * precision) / precision;
9449             }
9450             return result;
9451         },
9452
9453         /**
9454          * <p>Formats the passed number according to the passed format string.</p>
9455          * <p>The number of digits after the decimal separator character specifies the number of
9456          * decimal places in the resulting string. The <u>local-specific</u> decimal character is used in the result.</p>
9457          * <p>The <i>presence</i> of a thousand separator character in the format string specifies that
9458          * the <u>locale-specific</u> thousand separator (if any) is inserted separating thousand groups.</p>
9459          * <p>By default, "," is expected as the thousand separator, and "." is expected as the decimal separator.</p>
9460          * <p><b>New to Ext4</b></p>
9461          * <p>Locale-specific characters are always used in the formatted output when inserting
9462          * thousand and decimal separators.</p>
9463          * <p>The format string must specify separator characters according to US/UK conventions ("," as the
9464          * thousand separator, and "." as the decimal separator)</p>
9465          * <p>To allow specification of format strings according to local conventions for separator characters, add
9466          * the string <code>/i</code> to the end of the format string.</p>
9467          * <div style="margin-left:40px">examples (123456.789):
9468          * <div style="margin-left:10px">
9469          * 0 - (123456) show only digits, no precision<br>
9470          * 0.00 - (123456.78) show only digits, 2 precision<br>
9471          * 0.0000 - (123456.7890) show only digits, 4 precision<br>
9472          * 0,000 - (123,456) show comma and digits, no precision<br>
9473          * 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
9474          * 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
9475          * To allow specification of the formatting string using UK/US grouping characters (,) and decimal (.) for international numbers, add /i to the end.
9476          * For example: 0.000,00/i
9477          * </div></div>
9478          * @param {Number} v The number to format.
9479          * @param {String} format The way you would like to format this text.
9480          * @return {String} The formatted number.
9481          */
9482         number:
9483             function(v, formatString) {
9484             if (!formatString) {
9485                 return v;
9486             }
9487             v = Ext.Number.from(v, NaN);
9488             if (isNaN(v)) {
9489                 return '';
9490             }
9491             var comma = UtilFormat.thousandSeparator,
9492                 dec   = UtilFormat.decimalSeparator,
9493                 i18n  = false,
9494                 neg   = v < 0,
9495                 hasComma,
9496                 psplit;
9497
9498             v = Math.abs(v);
9499
9500             // The "/i" suffix allows caller to use a locale-specific formatting string.
9501             // Clean the format string by removing all but numerals and the decimal separator.
9502             // Then split the format string into pre and post decimal segments according to *what* the
9503             // decimal separator is. If they are specifying "/i", they are using the local convention in the format string.
9504             if (formatString.substr(formatString.length - 2) == '/i') {
9505                 if (!I18NFormatCleanRe) {
9506                     I18NFormatCleanRe = new RegExp('[^\\d\\' + UtilFormat.decimalSeparator + ']','g');
9507                 }
9508                 formatString = formatString.substr(0, formatString.length - 2);
9509                 i18n   = true;
9510                 hasComma = formatString.indexOf(comma) != -1;
9511                 psplit = formatString.replace(I18NFormatCleanRe, '').split(dec);
9512             } else {
9513                 hasComma = formatString.indexOf(',') != -1;
9514                 psplit = formatString.replace(formatCleanRe, '').split('.');
9515             }
9516
9517             if (1 < psplit.length) {
9518                 v = v.toFixed(psplit[1].length);
9519             } else if(2 < psplit.length) {
9520                 Ext.Error.raise({
9521                     sourceClass: "Ext.util.Format",
9522                     sourceMethod: "number",
9523                     value: v,
9524                     formatString: formatString,
9525                     msg: "Invalid number format, should have no more than 1 decimal"
9526                 });
9527             } else {
9528                 v = v.toFixed(0);
9529             }
9530
9531             var fnum = v.toString();
9532
9533             psplit = fnum.split('.');
9534
9535             if (hasComma) {
9536                 var cnum = psplit[0],
9537                     parr = [],
9538                     j    = cnum.length,
9539                     m    = Math.floor(j / 3),
9540                     n    = cnum.length % 3 || 3,
9541                     i;
9542
9543                 for (i = 0; i < j; i += n) {
9544                     if (i !== 0) {
9545                         n = 3;
9546                     }
9547
9548                     parr[parr.length] = cnum.substr(i, n);
9549                     m -= 1;
9550                 }
9551                 fnum = parr.join(comma);
9552                 if (psplit[1]) {
9553                     fnum += dec + psplit[1];
9554                 }
9555             } else {
9556                 if (psplit[1]) {
9557                     fnum = psplit[0] + dec + psplit[1];
9558                 }
9559             }
9560
9561             return (neg ? '-' : '') + formatString.replace(/[\d,?\.?]+/, fnum);
9562         },
9563
9564         /**
9565          * Returns a number rendering function that can be reused to apply a number format multiple times efficiently
9566          * @param {String} format Any valid number format string for {@link #number}
9567          * @return {Function} The number formatting function
9568          */
9569         numberRenderer : function(format) {
9570             return function(v) {
9571                 return UtilFormat.number(v, format);
9572             };
9573         },
9574
9575         /**
9576          * Selectively do a plural form of a word based on a numeric value. For example, in a template,
9577          * {commentCount:plural("Comment")}  would result in "1 Comment" if commentCount was 1 or would be "x Comments"
9578          * if the value is 0 or greater than 1.
9579          * @param {Number} value The value to compare against
9580          * @param {String} singular The singular form of the word
9581          * @param {String} plural (optional) The plural form of the word (defaults to the singular with an "s")
9582          */
9583         plural : function(v, s, p) {
9584             return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
9585         },
9586
9587         /**
9588          * Converts newline characters to the HTML tag &lt;br/>
9589          * @param {String} The string value to format.
9590          * @return {String} The string with embedded &lt;br/> tags in place of newlines.
9591          */
9592         nl2br : function(v) {
9593             return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
9594         },
9595
9596         /**
9597          * Capitalize the given string. See {@link Ext.String#capitalize}.
9598          * @method
9599          */
9600         capitalize: Ext.String.capitalize,
9601
9602         /**
9603          * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length.
9604          * See {@link Ext.String#ellipsis}.
9605          * @method
9606          */
9607         ellipsis: Ext.String.ellipsis,
9608
9609         /**
9610          * Formats to a string. See {@link Ext.String#format}
9611          * @method
9612          */
9613         format: Ext.String.format,
9614
9615         /**
9616          * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
9617          * See {@link Ext.string#htmlDecode}.
9618          * @method
9619          */
9620         htmlDecode: Ext.String.htmlDecode,
9621
9622         /**
9623          * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
9624          * See {@link Ext.String#htmlEncode}.
9625          * @method
9626          */
9627         htmlEncode: Ext.String.htmlEncode,
9628
9629         /**
9630          * Adds left padding to a string. See {@link Ext.String#leftPad}
9631          * @method
9632          */
9633         leftPad: Ext.String.leftPad,
9634
9635         /**
9636          * Trims any whitespace from either side of a string. See {@link Ext.String#trim}.
9637          * @method
9638          */
9639         trim : Ext.String.trim,
9640
9641         /**
9642          * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
9643          * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
9644          * @param {Number|String} v The encoded margins
9645          * @return {Object} An object with margin sizes for top, right, bottom and left
9646          */
9647         parseBox : function(box) {
9648             if (Ext.isNumber(box)) {
9649                 box = box.toString();
9650             }
9651             var parts  = box.split(' '),
9652                 ln = parts.length;
9653
9654             if (ln == 1) {
9655                 parts[1] = parts[2] = parts[3] = parts[0];
9656             }
9657             else if (ln == 2) {
9658                 parts[2] = parts[0];
9659                 parts[3] = parts[1];
9660             }
9661             else if (ln == 3) {
9662                 parts[3] = parts[1];
9663             }
9664
9665             return {
9666                 top   :parseInt(parts[0], 10) || 0,
9667                 right :parseInt(parts[1], 10) || 0,
9668                 bottom:parseInt(parts[2], 10) || 0,
9669                 left  :parseInt(parts[3], 10) || 0
9670             };
9671         },
9672
9673         /**
9674          * Escapes the passed string for use in a regular expression
9675          * @param {String} str
9676          * @return {String}
9677          */
9678         escapeRegex : function(s) {
9679             return s.replace(/([\-.*+?\^${}()|\[\]\/\\])/g, "\\$1");
9680         }
9681     });
9682 })();
9683
9684 /**
9685  * @class Ext.util.TaskRunner
9686  * Provides the ability to execute one or more arbitrary tasks in a multithreaded
9687  * manner.  Generally, you can use the singleton {@link Ext.TaskManager} instead, but
9688  * if needed, you can create separate instances of TaskRunner.  Any number of
9689  * separate tasks can be started at any time and will run independently of each
9690  * other. Example usage:
9691  * <pre><code>
9692 // Start a simple clock task that updates a div once per second
9693 var updateClock = function(){
9694     Ext.fly('clock').update(new Date().format('g:i:s A'));
9695
9696 var task = {
9697     run: updateClock,
9698     interval: 1000 //1 second
9699 }
9700 var runner = new Ext.util.TaskRunner();
9701 runner.start(task);
9702
9703 // equivalent using TaskManager
9704 Ext.TaskManager.start({
9705     run: updateClock,
9706     interval: 1000
9707 });
9708
9709  * </code></pre>
9710  * <p>See the {@link #start} method for details about how to configure a task object.</p>
9711  * Also see {@link Ext.util.DelayedTask}. 
9712  * 
9713  * @constructor
9714  * @param {Number} interval (optional) The minimum precision in milliseconds supported by this TaskRunner instance
9715  * (defaults to 10)
9716  */
9717 Ext.ns('Ext.util');
9718
9719 Ext.util.TaskRunner = function(interval) {
9720     interval = interval || 10;
9721     var tasks = [],
9722     removeQueue = [],
9723     id = 0,
9724     running = false,
9725
9726     // private
9727     stopThread = function() {
9728         running = false;
9729         clearInterval(id);
9730         id = 0;
9731     },
9732
9733     // private
9734     startThread = function() {
9735         if (!running) {
9736             running = true;
9737             id = setInterval(runTasks, interval);
9738         }
9739     },
9740
9741     // private
9742     removeTask = function(t) {
9743         removeQueue.push(t);
9744         if (t.onStop) {
9745             t.onStop.apply(t.scope || t);
9746         }
9747     },
9748
9749     // private
9750     runTasks = function() {
9751         var rqLen = removeQueue.length,
9752             now = new Date().getTime(),
9753             i;
9754
9755         if (rqLen > 0) {
9756             for (i = 0; i < rqLen; i++) {
9757                 Ext.Array.remove(tasks, removeQueue[i]);
9758             }
9759             removeQueue = [];
9760             if (tasks.length < 1) {
9761                 stopThread();
9762                 return;
9763             }
9764         }
9765         i = 0;
9766         var t,
9767             itime,
9768             rt,
9769             len = tasks.length;
9770         for (; i < len; ++i) {
9771             t = tasks[i];
9772             itime = now - t.taskRunTime;
9773             if (t.interval <= itime) {
9774                 rt = t.run.apply(t.scope || t, t.args || [++t.taskRunCount]);
9775                 t.taskRunTime = now;
9776                 if (rt === false || t.taskRunCount === t.repeat) {
9777                     removeTask(t);
9778                     return;
9779                 }
9780             }
9781             if (t.duration && t.duration <= (now - t.taskStartTime)) {
9782                 removeTask(t);
9783             }
9784         }
9785     };
9786
9787     /**
9788      * Starts a new task.
9789      * @method start
9790      * @param {Object} task <p>A config object that supports the following properties:<ul>
9791      * <li><code>run</code> : Function<div class="sub-desc"><p>The function to execute each time the task is invoked. The
9792      * function will be called at each interval and passed the <code>args</code> argument if specified, and the
9793      * current invocation count if not.</p>
9794      * <p>If a particular scope (<code>this</code> reference) is required, be sure to specify it using the <code>scope</code> argument.</p>
9795      * <p>Return <code>false</code> from this function to terminate the task.</p></div></li>
9796      * <li><code>interval</code> : Number<div class="sub-desc">The frequency in milliseconds with which the task
9797      * should be invoked.</div></li>
9798      * <li><code>args</code> : Array<div class="sub-desc">(optional) An array of arguments to be passed to the function
9799      * specified by <code>run</code>. If not specified, the current invocation count is passed.</div></li>
9800      * <li><code>scope</code> : Object<div class="sub-desc">(optional) The scope (<tt>this</tt> reference) in which to execute the
9801      * <code>run</code> function. Defaults to the task config object.</div></li>
9802      * <li><code>duration</code> : Number<div class="sub-desc">(optional) The length of time in milliseconds to invoke
9803      * the task before stopping automatically (defaults to indefinite).</div></li>
9804      * <li><code>repeat</code> : Number<div class="sub-desc">(optional) The number of times to invoke the task before
9805      * stopping automatically (defaults to indefinite).</div></li>
9806      * </ul></p>
9807      * <p>Before each invocation, Ext injects the property <code>taskRunCount</code> into the task object so
9808      * that calculations based on the repeat count can be performed.</p>
9809      * @return {Object} The task
9810      */
9811     this.start = function(task) {
9812         tasks.push(task);
9813         task.taskStartTime = new Date().getTime();
9814         task.taskRunTime = 0;
9815         task.taskRunCount = 0;
9816         startThread();
9817         return task;
9818     };
9819
9820     /**
9821      * Stops an existing running task.
9822      * @method stop
9823      * @param {Object} task The task to stop
9824      * @return {Object} The task
9825      */
9826     this.stop = function(task) {
9827         removeTask(task);
9828         return task;
9829     };
9830
9831     /**
9832      * Stops all tasks that are currently running.
9833      * @method stopAll
9834      */
9835     this.stopAll = function() {
9836         stopThread();
9837         for (var i = 0, len = tasks.length; i < len; i++) {
9838             if (tasks[i].onStop) {
9839                 tasks[i].onStop();
9840             }
9841         }
9842         tasks = [];
9843         removeQueue = [];
9844     };
9845 };
9846
9847 /**
9848  * @class Ext.TaskManager
9849  * @extends Ext.util.TaskRunner
9850  * A static {@link Ext.util.TaskRunner} instance that can be used to start and stop arbitrary tasks.  See
9851  * {@link Ext.util.TaskRunner} for supported methods and task config properties.
9852  * <pre><code>
9853 // Start a simple clock task that updates a div once per second
9854 var task = {
9855     run: function(){
9856         Ext.fly('clock').update(new Date().format('g:i:s A'));
9857     },
9858     interval: 1000 //1 second
9859 }
9860 Ext.TaskManager.start(task);
9861 </code></pre>
9862  * <p>See the {@link #start} method for details about how to configure a task object.</p>
9863  * @singleton
9864  */
9865 Ext.TaskManager = Ext.create('Ext.util.TaskRunner');
9866 /**
9867  * @class Ext.is
9868  * 
9869  * Determines information about the current platform the application is running on.
9870  * 
9871  * @singleton
9872  */
9873 Ext.is = {
9874     init : function(navigator) {
9875         var platforms = this.platforms,
9876             ln = platforms.length,
9877             i, platform;
9878
9879         navigator = navigator || window.navigator;
9880
9881         for (i = 0; i < ln; i++) {
9882             platform = platforms[i];
9883             this[platform.identity] = platform.regex.test(navigator[platform.property]);
9884         }
9885
9886         /**
9887          * @property Desktop True if the browser is running on a desktop machine
9888          * @type {Boolean}
9889          */
9890         this.Desktop = this.Mac || this.Windows || (this.Linux && !this.Android);
9891         /**
9892          * @property Tablet True if the browser is running on a tablet (iPad)
9893          */
9894         this.Tablet = this.iPad;
9895         /**
9896          * @property Phone True if the browser is running on a phone.
9897          * @type {Boolean}
9898          */
9899         this.Phone = !this.Desktop && !this.Tablet;
9900         /**
9901          * @property iOS True if the browser is running on iOS
9902          * @type {Boolean}
9903          */
9904         this.iOS = this.iPhone || this.iPad || this.iPod;
9905         
9906         /**
9907          * @property Standalone Detects when application has been saved to homescreen.
9908          * @type {Boolean}
9909          */
9910         this.Standalone = !!window.navigator.standalone;
9911     },
9912     
9913     /**
9914      * @property iPhone True when the browser is running on a iPhone
9915      * @type {Boolean}
9916      */
9917     platforms: [{
9918         property: 'platform',
9919         regex: /iPhone/i,
9920         identity: 'iPhone'
9921     },
9922     
9923     /**
9924      * @property iPod True when the browser is running on a iPod
9925      * @type {Boolean}
9926      */
9927     {
9928         property: 'platform',
9929         regex: /iPod/i,
9930         identity: 'iPod'
9931     },
9932     
9933     /**
9934      * @property iPad True when the browser is running on a iPad
9935      * @type {Boolean}
9936      */
9937     {
9938         property: 'userAgent',
9939         regex: /iPad/i,
9940         identity: 'iPad'
9941     },
9942     
9943     /**
9944      * @property Blackberry True when the browser is running on a Blackberry
9945      * @type {Boolean}
9946      */
9947     {
9948         property: 'userAgent',
9949         regex: /Blackberry/i,
9950         identity: 'Blackberry'
9951     },
9952     
9953     /**
9954      * @property Android True when the browser is running on an Android device
9955      * @type {Boolean}
9956      */
9957     {
9958         property: 'userAgent',
9959         regex: /Android/i,
9960         identity: 'Android'
9961     },
9962     
9963     /**
9964      * @property Mac True when the browser is running on a Mac
9965      * @type {Boolean}
9966      */
9967     {
9968         property: 'platform',
9969         regex: /Mac/i,
9970         identity: 'Mac'
9971     },
9972     
9973     /**
9974      * @property Windows True when the browser is running on Windows
9975      * @type {Boolean}
9976      */
9977     {
9978         property: 'platform',
9979         regex: /Win/i,
9980         identity: 'Windows'
9981     },
9982     
9983     /**
9984      * @property Linux True when the browser is running on Linux
9985      * @type {Boolean}
9986      */
9987     {
9988         property: 'platform',
9989         regex: /Linux/i,
9990         identity: 'Linux'
9991     }]
9992 };
9993
9994 Ext.is.init();
9995
9996 /**
9997  * @class Ext.supports
9998  *
9999  * Determines information about features are supported in the current environment
10000  * 
10001  * @singleton
10002  */
10003 Ext.supports = {
10004     init : function() {
10005         var doc = document,
10006             div = doc.createElement('div'),
10007             tests = this.tests,
10008             ln = tests.length,
10009             i, test;
10010
10011         div.innerHTML = [
10012             '<div style="height:30px;width:50px;">',
10013                 '<div style="height:20px;width:20px;"></div>',
10014             '</div>',
10015             '<div style="width: 200px; height: 200px; position: relative; padding: 5px;">',
10016                 '<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>',
10017             '</div>',
10018             '<div style="float:left; background-color:transparent;"></div>'
10019         ].join('');
10020
10021         doc.body.appendChild(div);
10022
10023         for (i = 0; i < ln; i++) {
10024             test = tests[i];
10025             this[test.identity] = test.fn.call(this, doc, div);
10026         }
10027
10028         doc.body.removeChild(div);
10029     },
10030
10031     /**
10032      * @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style.
10033      * @type {Boolean}
10034      */
10035     CSS3BoxShadow: Ext.isDefined(document.documentElement.style.boxShadow),
10036
10037     /**
10038      * @property ClassList True if document environment supports the HTML5 classList API.
10039      * @type {Boolean}
10040      */
10041     ClassList: !!document.documentElement.classList,
10042
10043     /**
10044      * @property OrientationChange True if the device supports orientation change
10045      * @type {Boolean}
10046      */
10047     OrientationChange: ((typeof window.orientation != 'undefined') && ('onorientationchange' in window)),
10048     
10049     /**
10050      * @property DeviceMotion True if the device supports device motion (acceleration and rotation rate)
10051      * @type {Boolean}
10052      */
10053     DeviceMotion: ('ondevicemotion' in window),
10054     
10055     /**
10056      * @property Touch True if the device supports touch
10057      * @type {Boolean}
10058      */
10059     // is.Desktop is needed due to the bug in Chrome 5.0.375, Safari 3.1.2
10060     // and Safari 4.0 (they all have 'ontouchstart' in the window object).
10061     Touch: ('ontouchstart' in window) && (!Ext.is.Desktop),
10062
10063     tests: [
10064         /**
10065          * @property Transitions True if the device supports CSS3 Transitions
10066          * @type {Boolean}
10067          */
10068         {
10069             identity: 'Transitions',
10070             fn: function(doc, div) {
10071                 var prefix = [
10072                         'webkit',
10073                         'Moz',
10074                         'o',
10075                         'ms',
10076                         'khtml'
10077                     ],
10078                     TE = 'TransitionEnd',
10079                     transitionEndName = [
10080                         prefix[0] + TE,
10081                         'transitionend', //Moz bucks the prefixing convention
10082                         prefix[2] + TE,
10083                         prefix[3] + TE,
10084                         prefix[4] + TE
10085                     ],
10086                     ln = prefix.length,
10087                     i = 0,
10088                     out = false;
10089                 div = Ext.get(div);
10090                 for (; i < ln; i++) {
10091                     if (div.getStyle(prefix[i] + "TransitionProperty")) {
10092                         Ext.supports.CSS3Prefix = prefix[i];
10093                         Ext.supports.CSS3TransitionEnd = transitionEndName[i];
10094                         out = true;
10095                         break;
10096                     }
10097                 }
10098                 return out;
10099             }
10100         },
10101         
10102         /**
10103          * @property RightMargin True if the device supports right margin.
10104          * See https://bugs.webkit.org/show_bug.cgi?id=13343 for why this is needed.
10105          * @type {Boolean}
10106          */
10107         {
10108             identity: 'RightMargin',
10109             fn: function(doc, div) {
10110                 var view = doc.defaultView;
10111                 return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px');
10112             }
10113         },
10114
10115         /**
10116          * @property DisplayChangeInputSelectionBug True if INPUT elements lose their
10117          * selection when their display style is changed. Essentially, if a text input
10118          * has focus and its display style is changed, the I-beam disappears.
10119          * 
10120          * This bug is encountered due to the work around in place for the {@link RightMargin}
10121          * bug. This has been observed in Safari 4.0.4 and older, and appears to be fixed
10122          * in Safari 5. It's not clear if Safari 4.1 has the bug, but it has the same WebKit
10123          * version number as Safari 5 (according to http://unixpapa.com/js/gecko.html).
10124          */
10125         {
10126             identity: 'DisplayChangeInputSelectionBug',
10127             fn: function() {
10128                 var webKitVersion = Ext.webKitVersion;
10129                 // WebKit but older than Safari 5 or Chrome 6:
10130                 return 0 < webKitVersion && webKitVersion < 533;
10131             }
10132         },
10133
10134         /**
10135          * @property DisplayChangeTextAreaSelectionBug True if TEXTAREA elements lose their
10136          * selection when their display style is changed. Essentially, if a text area has
10137          * focus and its display style is changed, the I-beam disappears.
10138          *
10139          * This bug is encountered due to the work around in place for the {@link RightMargin}
10140          * bug. This has been observed in Chrome 10 and Safari 5 and older, and appears to
10141          * be fixed in Chrome 11.
10142          */
10143         {
10144             identity: 'DisplayChangeTextAreaSelectionBug',
10145             fn: function() {
10146                 var webKitVersion = Ext.webKitVersion;
10147
10148                 /*
10149                 Has bug w/textarea:
10150
10151                 (Chrome) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US)
10152                             AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127
10153                             Safari/534.16
10154                 (Safari) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us)
10155                             AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5
10156                             Safari/533.21.1
10157
10158                 No bug:
10159
10160                 (Chrome) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7)
10161                             AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.57
10162                             Safari/534.24
10163                 */
10164                 return 0 < webKitVersion && webKitVersion < 534.24;
10165             }
10166         },
10167
10168         /**
10169          * @property TransparentColor True if the device supports transparent color
10170          * @type {Boolean}
10171          */
10172         {
10173             identity: 'TransparentColor',
10174             fn: function(doc, div, view) {
10175                 view = doc.defaultView;
10176                 return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent');
10177             }
10178         },
10179
10180         /**
10181          * @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle()
10182          * @type {Boolean}
10183          */
10184         {
10185             identity: 'ComputedStyle',
10186             fn: function(doc, div, view) {
10187                 view = doc.defaultView;
10188                 return view && view.getComputedStyle;
10189             }
10190         },
10191         
10192         /**
10193          * @property SVG True if the device supports SVG
10194          * @type {Boolean}
10195          */
10196         {
10197             identity: 'Svg',
10198             fn: function(doc) {
10199                 return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect;
10200             }
10201         },
10202     
10203         /**
10204          * @property Canvas True if the device supports Canvas
10205          * @type {Boolean}
10206          */
10207         {
10208             identity: 'Canvas',
10209             fn: function(doc) {
10210                 return !!doc.createElement('canvas').getContext;
10211             }
10212         },
10213         
10214         /**
10215          * @property VML True if the device supports VML
10216          * @type {Boolean}
10217          */
10218         {
10219             identity: 'Vml',
10220             fn: function(doc) {
10221                 var d = doc.createElement("div");
10222                 d.innerHTML = "<!--[if vml]><br><br><![endif]-->";
10223                 return (d.childNodes.length == 2);
10224             }
10225         },
10226         
10227         /**
10228          * @property Float True if the device supports CSS float
10229          * @type {Boolean}
10230          */
10231         {
10232             identity: 'Float',
10233             fn: function(doc, div) {
10234                 return !!div.lastChild.style.cssFloat;
10235             }
10236         },
10237         
10238         /**
10239          * @property AudioTag True if the device supports the HTML5 audio tag
10240          * @type {Boolean}
10241          */
10242         {
10243             identity: 'AudioTag',
10244             fn: function(doc) {
10245                 return !!doc.createElement('audio').canPlayType;
10246             }
10247         },
10248         
10249         /**
10250          * @property History True if the device supports HTML5 history
10251          * @type {Boolean}
10252          */
10253         {
10254             identity: 'History',
10255             fn: function() {
10256                 return !!(window.history && history.pushState);
10257             }
10258         },
10259         
10260         /**
10261          * @property CSS3DTransform True if the device supports CSS3DTransform
10262          * @type {Boolean}
10263          */
10264         {
10265             identity: 'CSS3DTransform',
10266             fn: function() {
10267                 return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41'));
10268             }
10269         },
10270
10271                 /**
10272          * @property CSS3LinearGradient True if the device supports CSS3 linear gradients
10273          * @type {Boolean}
10274          */
10275         {
10276             identity: 'CSS3LinearGradient',
10277             fn: function(doc, div) {
10278                 var property = 'background-image:',
10279                     webkit   = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))',
10280                     w3c      = 'linear-gradient(left top, black, white)',
10281                     moz      = '-moz-' + w3c,
10282                     options  = [property + webkit, property + w3c, property + moz];
10283                 
10284                 div.style.cssText = options.join(';');
10285                 
10286                 return ("" + div.style.backgroundImage).indexOf('gradient') !== -1;
10287             }
10288         },
10289         
10290         /**
10291          * @property CSS3BorderRadius True if the device supports CSS3 border radius
10292          * @type {Boolean}
10293          */
10294         {
10295             identity: 'CSS3BorderRadius',
10296             fn: function(doc, div) {
10297                 var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'],
10298                     pass = false,
10299                     i;
10300                 for (i = 0; i < domPrefixes.length; i++) {
10301                     if (document.body.style[domPrefixes[i]] !== undefined) {
10302                         return true;
10303                     }
10304                 }
10305                 return pass;
10306             }
10307         },
10308         
10309         /**
10310          * @property GeoLocation True if the device supports GeoLocation
10311          * @type {Boolean}
10312          */
10313         {
10314             identity: 'GeoLocation',
10315             fn: function() {
10316                 return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (typeof google != 'undefined' && typeof google.gears != 'undefined');
10317             }
10318         },
10319         /**
10320          * @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events
10321          * @type {Boolean}
10322          */
10323         {
10324             identity: 'MouseEnterLeave',
10325             fn: function(doc, div){
10326                 return ('onmouseenter' in div && 'onmouseleave' in div);
10327             }
10328         },
10329         /**
10330          * @property MouseWheel True if the browser supports the mousewheel event
10331          * @type {Boolean}
10332          */
10333         {
10334             identity: 'MouseWheel',
10335             fn: function(doc, div) {
10336                 return ('onmousewheel' in div);
10337             }
10338         },
10339         /**
10340          * @property Opacity True if the browser supports normal css opacity
10341          * @type {Boolean}
10342          */
10343         {
10344             identity: 'Opacity',
10345             fn: function(doc, div){
10346                 // Not a strict equal comparison in case opacity can be converted to a number.
10347                 if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
10348                     return false;
10349                 }
10350                 div.firstChild.style.cssText = 'opacity:0.73';
10351                 return div.firstChild.style.opacity == '0.73';
10352             }
10353         },
10354         /**
10355          * @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs
10356          * @type {Boolean}
10357          */
10358         {
10359             identity: 'Placeholder',
10360             fn: function(doc) {
10361                 return 'placeholder' in doc.createElement('input');
10362             }
10363         },
10364         
10365         /**
10366          * @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight, 
10367          * getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel.
10368          * @type {Boolean}
10369          */
10370         {
10371             identity: 'Direct2DBug',
10372             fn: function() {
10373                 return Ext.isString(document.body.style.msTransformOrigin);
10374             }
10375         },
10376         /**
10377          * @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements
10378          * @type {Boolean}
10379          */
10380         {
10381             identity: 'BoundingClientRect',
10382             fn: function(doc, div) {
10383                 return Ext.isFunction(div.getBoundingClientRect);
10384             }
10385         },
10386         {
10387             identity: 'IncludePaddingInWidthCalculation',
10388             fn: function(doc, div){
10389                 var el = Ext.get(div.childNodes[1].firstChild);
10390                 return el.getWidth() == 210;
10391             }
10392         },
10393         {
10394             identity: 'IncludePaddingInHeightCalculation',
10395             fn: function(doc, div){
10396                 var el = Ext.get(div.childNodes[1].firstChild);
10397                 return el.getHeight() == 210;
10398             }
10399         },
10400         
10401         /**
10402          * @property ArraySort True if the Array sort native method isn't bugged.
10403          * @type {Boolean}
10404          */
10405         {
10406             identity: 'ArraySort',
10407             fn: function() {
10408                 var a = [1,2,3,4,5].sort(function(){ return 0; });
10409                 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
10410             }
10411         },
10412         /**
10413          * @property Range True if browser support document.createRange native method.
10414          * @type {Boolean}
10415          */
10416         {
10417             identity: 'Range',
10418             fn: function() {
10419                 return !!document.createRange;
10420             }
10421         },
10422         /**
10423          * @property CreateContextualFragment True if browser support CreateContextualFragment range native methods.
10424          * @type {Boolean}
10425          */
10426         {
10427             identity: 'CreateContextualFragment',
10428             fn: function() {
10429                 var range = Ext.supports.Range ? document.createRange() : false;
10430                 
10431                 return range && !!range.createContextualFragment;
10432             }
10433         },
10434
10435         /**
10436          * @property WindowOnError True if browser supports window.onerror.
10437          * @type {Boolean}
10438          */
10439         {
10440             identity: 'WindowOnError',
10441             fn: function () {
10442                 // sadly, we cannot feature detect this...
10443                 return Ext.isIE || Ext.isGecko || Ext.webKitVersion >= 534.16; // Chrome 10+
10444             }
10445         }
10446     ]
10447 };
10448
10449
10450
10451 /*
10452 Ext JS - JavaScript Library
10453 Copyright (c) 2006-2011, Sencha Inc.
10454 All rights reserved.
10455 licensing@sencha.com
10456 */
10457 /**
10458  * @class Ext.core.DomHelper
10459  * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
10460  * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
10461  * from your DOM building code.</p>
10462  *
10463  * <p><b><u>DomHelper element specification object</u></b></p>
10464  * <p>A specification object is used when creating elements. Attributes of this object
10465  * are assumed to be element attributes, except for 4 special attributes:
10466  * <div class="mdetail-params"><ul>
10467  * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
10468  * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
10469  * same kind of element definition objects to be created and appended. These can be nested
10470  * as deep as you want.</div></li>
10471  * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
10472  * This will end up being either the "class" attribute on a HTML fragment or className
10473  * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
10474  * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
10475  * </ul></div></p>
10476  * <p><b>NOTE:</b> For other arbitrary attributes, the value will currently <b>not</b> be automatically
10477  * HTML-escaped prior to building the element's HTML string. This means that if your attribute value
10478  * contains special characters that would not normally be allowed in a double-quoted attribute value,
10479  * you <b>must</b> manually HTML-encode it beforehand (see {@link Ext.String#htmlEncode}) or risk
10480  * malformed HTML being created. This behavior may change in a future release.</p>
10481  *
10482  * <p><b><u>Insertion methods</u></b></p>
10483  * <p>Commonly used insertion methods:
10484  * <div class="mdetail-params"><ul>
10485  * <li><b><tt>{@link #append}</tt></b> : <div class="sub-desc"></div></li>
10486  * <li><b><tt>{@link #insertBefore}</tt></b> : <div class="sub-desc"></div></li>
10487  * <li><b><tt>{@link #insertAfter}</tt></b> : <div class="sub-desc"></div></li>
10488  * <li><b><tt>{@link #overwrite}</tt></b> : <div class="sub-desc"></div></li>
10489  * <li><b><tt>{@link #createTemplate}</tt></b> : <div class="sub-desc"></div></li>
10490  * <li><b><tt>{@link #insertHtml}</tt></b> : <div class="sub-desc"></div></li>
10491  * </ul></div></p>
10492  *
10493  * <p><b><u>Example</u></b></p>
10494  * <p>This is an example, where an unordered list with 3 children items is appended to an existing
10495  * element with id <tt>'my-div'</tt>:<br>
10496  <pre><code>
10497 var dh = Ext.core.DomHelper; // create shorthand alias
10498 // specification object
10499 var spec = {
10500     id: 'my-ul',
10501     tag: 'ul',
10502     cls: 'my-list',
10503     // append children after creating
10504     children: [     // may also specify 'cn' instead of 'children'
10505         {tag: 'li', id: 'item0', html: 'List Item 0'},
10506         {tag: 'li', id: 'item1', html: 'List Item 1'},
10507         {tag: 'li', id: 'item2', html: 'List Item 2'}
10508     ]
10509 };
10510 var list = dh.append(
10511     'my-div', // the context element 'my-div' can either be the id or the actual node
10512     spec      // the specification object
10513 );
10514  </code></pre></p>
10515  * <p>Element creation specification parameters in this class may also be passed as an Array of
10516  * specification objects. This can be used to insert multiple sibling nodes into an existing
10517  * container very efficiently. For example, to add more list items to the example above:<pre><code>
10518 dh.append('my-ul', [
10519     {tag: 'li', id: 'item3', html: 'List Item 3'},
10520     {tag: 'li', id: 'item4', html: 'List Item 4'}
10521 ]);
10522  * </code></pre></p>
10523  *
10524  * <p><b><u>Templating</u></b></p>
10525  * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
10526  * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
10527  * insert new elements. Revisiting the example above, we could utilize templating this time:
10528  * <pre><code>
10529 // create the node
10530 var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
10531 // get template
10532 var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
10533
10534 for(var i = 0; i < 5, i++){
10535     tpl.append(list, [i]); // use template to append to the actual node
10536 }
10537  * </code></pre></p>
10538  * <p>An example using a template:<pre><code>
10539 var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
10540
10541 var tpl = new Ext.core.DomHelper.createTemplate(html);
10542 tpl.append('blog-roll', ['link1', 'http://www.edspencer.net/', "Ed&#39;s Site"]);
10543 tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin&#39;s Site"]);
10544  * </code></pre></p>
10545  *
10546  * <p>The same example using named parameters:<pre><code>
10547 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
10548
10549 var tpl = new Ext.core.DomHelper.createTemplate(html);
10550 tpl.append('blog-roll', {
10551     id: 'link1',
10552     url: 'http://www.edspencer.net/',
10553     text: "Ed&#39;s Site"
10554 });
10555 tpl.append('blog-roll', {
10556     id: 'link2',
10557     url: 'http://www.dustindiaz.com/',
10558     text: "Dustin&#39;s Site"
10559 });
10560  * </code></pre></p>
10561  *
10562  * <p><b><u>Compiling Templates</u></b></p>
10563  * <p>Templates are applied using regular expressions. The performance is great, but if
10564  * you are adding a bunch of DOM elements using the same template, you can increase
10565  * performance even further by {@link Ext.Template#compile "compiling"} the template.
10566  * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
10567  * broken up at the different variable points and a dynamic function is created and eval'ed.
10568  * The generated function performs string concatenation of these parts and the passed
10569  * variables instead of using regular expressions.
10570  * <pre><code>
10571 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
10572
10573 var tpl = new Ext.core.DomHelper.createTemplate(html);
10574 tpl.compile();
10575
10576 //... use template like normal
10577  * </code></pre></p>
10578  *
10579  * <p><b><u>Performance Boost</u></b></p>
10580  * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
10581  * of DOM can significantly boost performance.</p>
10582  * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
10583  * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
10584  * results in the creation of a text node. Usage:</p>
10585  * <pre><code>
10586 Ext.core.DomHelper.useDom = true; // force it to use DOM; reduces performance
10587  * </code></pre>
10588  * @singleton
10589  */
10590 Ext.ns('Ext.core');
10591 Ext.core.DomHelper = function(){
10592     var tempTableEl = null,
10593         emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
10594         tableRe = /^table|tbody|tr|td$/i,
10595         confRe = /tag|children|cn|html$/i,
10596         tableElRe = /td|tr|tbody/i,
10597         endRe = /end/i,
10598         pub,
10599         // kill repeat to save bytes
10600         afterbegin = 'afterbegin',
10601         afterend = 'afterend',
10602         beforebegin = 'beforebegin',
10603         beforeend = 'beforeend',
10604         ts = '<table>',
10605         te = '</table>',
10606         tbs = ts+'<tbody>',
10607         tbe = '</tbody>'+te,
10608         trs = tbs + '<tr>',
10609         tre = '</tr>'+tbe;
10610
10611     // private
10612     function doInsert(el, o, returnElement, pos, sibling, append){
10613         el = Ext.getDom(el);
10614         var newNode;
10615         if (pub.useDom) {
10616             newNode = createDom(o, null);
10617             if (append) {
10618                 el.appendChild(newNode);
10619             } else {
10620                 (sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
10621             }
10622         } else {
10623             newNode = Ext.core.DomHelper.insertHtml(pos, el, Ext.core.DomHelper.createHtml(o));
10624         }
10625         return returnElement ? Ext.get(newNode, true) : newNode;
10626     }
10627     
10628     function createDom(o, parentNode){
10629         var el,
10630             doc = document,
10631             useSet,
10632             attr,
10633             val,
10634             cn;
10635
10636         if (Ext.isArray(o)) {                       // Allow Arrays of siblings to be inserted
10637             el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
10638             for (var i = 0, l = o.length; i < l; i++) {
10639                 createDom(o[i], el);
10640             }
10641         } else if (typeof o == 'string') {         // Allow a string as a child spec.
10642             el = doc.createTextNode(o);
10643         } else {
10644             el = doc.createElement( o.tag || 'div' );
10645             useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
10646             for (attr in o) {
10647                 if(!confRe.test(attr)){
10648                     val = o[attr];
10649                     if(attr == 'cls'){
10650                         el.className = val;
10651                     }else{
10652                         if(useSet){
10653                             el.setAttribute(attr, val);
10654                         }else{
10655                             el[attr] = val;
10656                         }
10657                     }
10658                 }
10659             }
10660             Ext.core.DomHelper.applyStyles(el, o.style);
10661
10662             if ((cn = o.children || o.cn)) {
10663                 createDom(cn, el);
10664             } else if (o.html) {
10665                 el.innerHTML = o.html;
10666             }
10667         }
10668         if(parentNode){
10669            parentNode.appendChild(el);
10670         }
10671         return el;
10672     }
10673
10674     // build as innerHTML where available
10675     function createHtml(o){
10676         var b = '',
10677             attr,
10678             val,
10679             key,
10680             cn,
10681             i;
10682
10683         if(typeof o == "string"){
10684             b = o;
10685         } else if (Ext.isArray(o)) {
10686             for (i=0; i < o.length; i++) {
10687                 if(o[i]) {
10688                     b += createHtml(o[i]);
10689                 }
10690             }
10691         } else {
10692             b += '<' + (o.tag = o.tag || 'div');
10693             for (attr in o) {
10694                 val = o[attr];
10695                 if(!confRe.test(attr)){
10696                     if (typeof val == "object") {
10697                         b += ' ' + attr + '="';
10698                         for (key in val) {
10699                             b += key + ':' + val[key] + ';';
10700                         }
10701                         b += '"';
10702                     }else{
10703                         b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
10704                     }
10705                 }
10706             }
10707             // Now either just close the tag or try to add children and close the tag.
10708             if (emptyTags.test(o.tag)) {
10709                 b += '/>';
10710             } else {
10711                 b += '>';
10712                 if ((cn = o.children || o.cn)) {
10713                     b += createHtml(cn);
10714                 } else if(o.html){
10715                     b += o.html;
10716                 }
10717                 b += '</' + o.tag + '>';
10718             }
10719         }
10720         return b;
10721     }
10722
10723     function ieTable(depth, s, h, e){
10724         tempTableEl.innerHTML = [s, h, e].join('');
10725         var i = -1,
10726             el = tempTableEl,
10727             ns;
10728         while(++i < depth){
10729             el = el.firstChild;
10730         }
10731 //      If the result is multiple siblings, then encapsulate them into one fragment.
10732         ns = el.nextSibling;
10733         if (ns){
10734             var df = document.createDocumentFragment();
10735             while(el){
10736                 ns = el.nextSibling;
10737                 df.appendChild(el);
10738                 el = ns;
10739             }
10740             el = df;
10741         }
10742         return el;
10743     }
10744
10745     /**
10746      * @ignore
10747      * Nasty code for IE's broken table implementation
10748      */
10749     function insertIntoTable(tag, where, el, html) {
10750         var node,
10751             before;
10752
10753         tempTableEl = tempTableEl || document.createElement('div');
10754
10755         if(tag == 'td' && (where == afterbegin || where == beforeend) ||
10756            !tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
10757             return null;
10758         }
10759         before = where == beforebegin ? el :
10760                  where == afterend ? el.nextSibling :
10761                  where == afterbegin ? el.firstChild : null;
10762
10763         if (where == beforebegin || where == afterend) {
10764             el = el.parentNode;
10765         }
10766
10767         if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
10768             node = ieTable(4, trs, html, tre);
10769         } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
10770                    (tag == 'tr' && (where == beforebegin || where == afterend))) {
10771             node = ieTable(3, tbs, html, tbe);
10772         } else {
10773             node = ieTable(2, ts, html, te);
10774         }
10775         el.insertBefore(node, before);
10776         return node;
10777     }
10778     
10779     /**
10780      * @ignore
10781      * Fix for IE9 createContextualFragment missing method
10782      */   
10783     function createContextualFragment(html){
10784         var div = document.createElement("div"),
10785             fragment = document.createDocumentFragment(),
10786             i = 0,
10787             length, childNodes;
10788         
10789         div.innerHTML = html;
10790         childNodes = div.childNodes;
10791         length = childNodes.length;
10792
10793         for (; i < length; i++) {
10794             fragment.appendChild(childNodes[i].cloneNode(true));
10795         }
10796
10797         return fragment;
10798     }
10799     
10800     pub = {
10801         /**
10802          * Returns the markup for the passed Element(s) config.
10803          * @param {Object} o The DOM object spec (and children)
10804          * @return {String}
10805          */
10806         markup : function(o){
10807             return createHtml(o);
10808         },
10809
10810         /**
10811          * Applies a style specification to an element.
10812          * @param {String/HTMLElement} el The element to apply styles to
10813          * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
10814          * a function which returns such a specification.
10815          */
10816         applyStyles : function(el, styles){
10817             if (styles) {
10818                 el = Ext.fly(el);
10819                 if (typeof styles == "function") {
10820                     styles = styles.call();
10821                 }
10822                 if (typeof styles == "string") {
10823                     styles = Ext.core.Element.parseStyles(styles);
10824                 }
10825                 if (typeof styles == "object") {
10826                     el.setStyle(styles);
10827                 }
10828             }
10829         },
10830
10831         /**
10832          * Inserts an HTML fragment into the DOM.
10833          * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
10834          * @param {HTMLElement/TextNode} el The context element
10835          * @param {String} html The HTML fragment
10836          * @return {HTMLElement} The new node
10837          */
10838         insertHtml : function(where, el, html){
10839             var hash = {},
10840                 hashVal,
10841                 range,
10842                 rangeEl,
10843                 setStart,
10844                 frag,
10845                 rs;
10846
10847             where = where.toLowerCase();
10848             // add these here because they are used in both branches of the condition.
10849             hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
10850             hash[afterend] = ['AfterEnd', 'nextSibling'];
10851             
10852             // if IE and context element is an HTMLElement
10853             if (el.insertAdjacentHTML) {
10854                 if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
10855                     return rs;
10856                 }
10857                 
10858                 // add these two to the hash.
10859                 hash[afterbegin] = ['AfterBegin', 'firstChild'];
10860                 hash[beforeend] = ['BeforeEnd', 'lastChild'];
10861                 if ((hashVal = hash[where])) {
10862                     el.insertAdjacentHTML(hashVal[0], html);
10863                     return el[hashVal[1]];
10864                 }
10865             // if (not IE and context element is an HTMLElement) or TextNode
10866             } else {
10867                 // we cannot insert anything inside a textnode so...
10868                 if (Ext.isTextNode(el)) {
10869                     where = where === 'afterbegin' ? 'beforebegin' : where; 
10870                     where = where === 'beforeend' ? 'afterend' : where;
10871                 }
10872                 range = Ext.supports.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
10873                 setStart = 'setStart' + (endRe.test(where) ? 'After' : 'Before');
10874                 if (hash[where]) {
10875                     if (range) {
10876                         range[setStart](el);
10877                         frag = range.createContextualFragment(html);
10878                     } else {
10879                         frag = createContextualFragment(html);
10880                     }
10881                     el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
10882                     return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
10883                 } else {
10884                     rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
10885                     if (el.firstChild) {
10886                         if (range) {
10887                             range[setStart](el[rangeEl]);
10888                             frag = range.createContextualFragment(html);
10889                         } else {
10890                             frag = createContextualFragment(html);
10891                         }
10892                         
10893                         if(where == afterbegin){
10894                             el.insertBefore(frag, el.firstChild);
10895                         }else{
10896                             el.appendChild(frag);
10897                         }
10898                     } else {
10899                         el.innerHTML = html;
10900                     }
10901                     return el[rangeEl];
10902                 }
10903             }
10904             Ext.Error.raise({
10905                 sourceClass: 'Ext.core.DomHelper',
10906                 sourceMethod: 'insertHtml',
10907                 htmlToInsert: html,
10908                 targetElement: el,
10909                 msg: 'Illegal insertion point reached: "' + where + '"'
10910             });
10911         },
10912
10913         /**
10914          * Creates new DOM element(s) and inserts them before el.
10915          * @param {Mixed} el The context element
10916          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
10917          * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
10918          * @return {HTMLElement/Ext.core.Element} The new node
10919          */
10920         insertBefore : function(el, o, returnElement){
10921             return doInsert(el, o, returnElement, beforebegin);
10922         },
10923
10924         /**
10925          * Creates new DOM element(s) and inserts them after el.
10926          * @param {Mixed} el The context element
10927          * @param {Object} o The DOM object spec (and children)
10928          * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
10929          * @return {HTMLElement/Ext.core.Element} The new node
10930          */
10931         insertAfter : function(el, o, returnElement){
10932             return doInsert(el, o, returnElement, afterend, 'nextSibling');
10933         },
10934
10935         /**
10936          * Creates new DOM element(s) and inserts them as the first child of el.
10937          * @param {Mixed} el The context element
10938          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
10939          * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
10940          * @return {HTMLElement/Ext.core.Element} The new node
10941          */
10942         insertFirst : function(el, o, returnElement){
10943             return doInsert(el, o, returnElement, afterbegin, 'firstChild');
10944         },
10945
10946         /**
10947          * Creates new DOM element(s) and appends them to el.
10948          * @param {Mixed} el The context element
10949          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
10950          * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
10951          * @return {HTMLElement/Ext.core.Element} The new node
10952          */
10953         append : function(el, o, returnElement){
10954             return doInsert(el, o, returnElement, beforeend, '', true);
10955         },
10956
10957         /**
10958          * Creates new DOM element(s) and overwrites the contents of el with them.
10959          * @param {Mixed} el The context element
10960          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
10961          * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
10962          * @return {HTMLElement/Ext.core.Element} The new node
10963          */
10964         overwrite : function(el, o, returnElement){
10965             el = Ext.getDom(el);
10966             el.innerHTML = createHtml(o);
10967             return returnElement ? Ext.get(el.firstChild) : el.firstChild;
10968         },
10969
10970         createHtml : createHtml,
10971         
10972         /**
10973          * Creates new DOM element(s) without inserting them to the document.
10974          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
10975          * @return {HTMLElement} The new uninserted node
10976          * @method
10977          */
10978         createDom: createDom,
10979         
10980         /** True to force the use of DOM instead of html fragments @type Boolean */
10981         useDom : false,
10982         
10983         /**
10984          * Creates a new Ext.Template from the DOM object spec.
10985          * @param {Object} o The DOM object spec (and children)
10986          * @return {Ext.Template} The new template
10987          */
10988         createTemplate : function(o){
10989             var html = Ext.core.DomHelper.createHtml(o);
10990             return Ext.create('Ext.Template', html);
10991         }
10992     };
10993     return pub;
10994 }();
10995
10996 /*
10997  * This is code is also distributed under MIT license for use
10998  * with jQuery and prototype JavaScript libraries.
10999  */
11000 /**
11001  * @class Ext.DomQuery
11002 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).
11003 <p>
11004 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>
11005
11006 <p>
11007 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.
11008 </p>
11009 <h4>Element Selectors:</h4>
11010 <ul class="list">
11011     <li> <b>*</b> any element</li>
11012     <li> <b>E</b> an element with the tag E</li>
11013     <li> <b>E F</b> All descendent elements of E that have the tag F</li>
11014     <li> <b>E > F</b> or <b>E/F</b> all direct children elements of E that have the tag F</li>
11015     <li> <b>E + F</b> all elements with the tag F that are immediately preceded by an element with the tag E</li>
11016     <li> <b>E ~ F</b> all elements with the tag F that are preceded by a sibling element with the tag E</li>
11017 </ul>
11018 <h4>Attribute Selectors:</h4>
11019 <p>The use of &#64; and quotes are optional. For example, div[&#64;foo='bar'] is also a valid attribute selector.</p>
11020 <ul class="list">
11021     <li> <b>E[foo]</b> has an attribute "foo"</li>
11022     <li> <b>E[foo=bar]</b> has an attribute "foo" that equals "bar"</li>
11023     <li> <b>E[foo^=bar]</b> has an attribute "foo" that starts with "bar"</li>
11024     <li> <b>E[foo$=bar]</b> has an attribute "foo" that ends with "bar"</li>
11025     <li> <b>E[foo*=bar]</b> has an attribute "foo" that contains the substring "bar"</li>
11026     <li> <b>E[foo%=2]</b> has an attribute "foo" that is evenly divisible by 2</li>
11027     <li> <b>E[foo!=bar]</b> attribute "foo" does not equal "bar"</li>
11028 </ul>
11029 <h4>Pseudo Classes:</h4>
11030 <ul class="list">
11031     <li> <b>E:first-child</b> E is the first child of its parent</li>
11032     <li> <b>E:last-child</b> E is the last child of its parent</li>
11033     <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>
11034     <li> <b>E:nth-child(odd)</b> E is an odd child of its parent</li>
11035     <li> <b>E:nth-child(even)</b> E is an even child of its parent</li>
11036     <li> <b>E:only-child</b> E is the only child of its parent</li>
11037     <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>
11038     <li> <b>E:first</b> the first E in the resultset</li>
11039     <li> <b>E:last</b> the last E in the resultset</li>
11040     <li> <b>E:nth(<i>n</i>)</b> the <i>n</i>th E in the resultset (1 based)</li>
11041     <li> <b>E:odd</b> shortcut for :nth-child(odd)</li>
11042     <li> <b>E:even</b> shortcut for :nth-child(even)</li>
11043     <li> <b>E:contains(foo)</b> E's innerHTML contains the substring "foo"</li>
11044     <li> <b>E:nodeValue(foo)</b> E contains a textNode with a nodeValue that equals "foo"</li>
11045     <li> <b>E:not(S)</b> an E element that does not match simple selector S</li>
11046     <li> <b>E:has(S)</b> an E element that has a descendent that matches simple selector S</li>
11047     <li> <b>E:next(S)</b> an E element whose next sibling matches simple selector S</li>
11048     <li> <b>E:prev(S)</b> an E element whose previous sibling matches simple selector S</li>
11049     <li> <b>E:any(S1|S2|S2)</b> an E element which matches any of the simple selectors S1, S2 or S3//\\</li>
11050 </ul>
11051 <h4>CSS Value Selectors:</h4>
11052 <ul class="list">
11053     <li> <b>E{display=none}</b> css value "display" that equals "none"</li>
11054     <li> <b>E{display^=none}</b> css value "display" that starts with "none"</li>
11055     <li> <b>E{display$=none}</b> css value "display" that ends with "none"</li>
11056     <li> <b>E{display*=none}</b> css value "display" that contains the substring "none"</li>
11057     <li> <b>E{display%=2}</b> css value "display" that is evenly divisible by 2</li>
11058     <li> <b>E{display!=none}</b> css value "display" that does not equal "none"</li>
11059 </ul>
11060  * @singleton
11061  */
11062 Ext.ns('Ext.core');
11063
11064 Ext.core.DomQuery = Ext.DomQuery = function(){
11065     var cache = {},
11066         simpleCache = {},
11067         valueCache = {},
11068         nonSpace = /\S/,
11069         trimRe = /^\s+|\s+$/g,
11070         tplRe = /\{(\d+)\}/g,
11071         modeRe = /^(\s?[\/>+~]\s?|\s|$)/,
11072         tagTokenRe = /^(#)?([\w-\*]+)/,
11073         nthRe = /(\d*)n\+?(\d*)/,
11074         nthRe2 = /\D/,
11075         // This is for IE MSXML which does not support expandos.
11076     // IE runs the same speed using setAttribute, however FF slows way down
11077     // and Safari completely fails so they need to continue to use expandos.
11078     isIE = window.ActiveXObject ? true : false,
11079     key = 30803;
11080
11081     // this eval is stop the compressor from
11082     // renaming the variable to something shorter
11083     eval("var batch = 30803;");
11084
11085     // Retrieve the child node from a particular
11086     // parent at the specified index.
11087     function child(parent, index){
11088         var i = 0,
11089             n = parent.firstChild;
11090         while(n){
11091             if(n.nodeType == 1){
11092                if(++i == index){
11093                    return n;
11094                }
11095             }
11096             n = n.nextSibling;
11097         }
11098         return null;
11099     }
11100
11101     // retrieve the next element node
11102     function next(n){
11103         while((n = n.nextSibling) && n.nodeType != 1);
11104         return n;
11105     }
11106
11107     // retrieve the previous element node
11108     function prev(n){
11109         while((n = n.previousSibling) && n.nodeType != 1);
11110         return n;
11111     }
11112
11113     // Mark each child node with a nodeIndex skipping and
11114     // removing empty text nodes.
11115     function children(parent){
11116         var n = parent.firstChild,
11117         nodeIndex = -1,
11118         nextNode;
11119         while(n){
11120             nextNode = n.nextSibling;
11121             // clean worthless empty nodes.
11122             if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){
11123             parent.removeChild(n);
11124             }else{
11125             // add an expando nodeIndex
11126             n.nodeIndex = ++nodeIndex;
11127             }
11128             n = nextNode;
11129         }
11130         return this;
11131     }
11132
11133
11134     // nodeSet - array of nodes
11135     // cls - CSS Class
11136     function byClassName(nodeSet, cls){
11137         if(!cls){
11138             return nodeSet;
11139         }
11140         var result = [], ri = -1;
11141         for(var i = 0, ci; ci = nodeSet[i]; i++){
11142             if((' '+ci.className+' ').indexOf(cls) != -1){
11143                 result[++ri] = ci;
11144             }
11145         }
11146         return result;
11147     };
11148
11149     function attrValue(n, attr){
11150         // if its an array, use the first node.
11151         if(!n.tagName && typeof n.length != "undefined"){
11152             n = n[0];
11153         }
11154         if(!n){
11155             return null;
11156         }
11157
11158         if(attr == "for"){
11159             return n.htmlFor;
11160         }
11161         if(attr == "class" || attr == "className"){
11162             return n.className;
11163         }
11164         return n.getAttribute(attr) || n[attr];
11165
11166     };
11167
11168
11169     // ns - nodes
11170     // mode - false, /, >, +, ~
11171     // tagName - defaults to "*"
11172     function getNodes(ns, mode, tagName){
11173         var result = [], ri = -1, cs;
11174         if(!ns){
11175             return result;
11176         }
11177         tagName = tagName || "*";
11178         // convert to array
11179         if(typeof ns.getElementsByTagName != "undefined"){
11180             ns = [ns];
11181         }
11182
11183         // no mode specified, grab all elements by tagName
11184         // at any depth
11185         if(!mode){
11186             for(var i = 0, ni; ni = ns[i]; i++){
11187                 cs = ni.getElementsByTagName(tagName);
11188                 for(var j = 0, ci; ci = cs[j]; j++){
11189                     result[++ri] = ci;
11190                 }
11191             }
11192         // Direct Child mode (/ or >)
11193         // E > F or E/F all direct children elements of E that have the tag
11194         } else if(mode == "/" || mode == ">"){
11195             var utag = tagName.toUpperCase();
11196             for(var i = 0, ni, cn; ni = ns[i]; i++){
11197                 cn = ni.childNodes;
11198                 for(var j = 0, cj; cj = cn[j]; j++){
11199                     if(cj.nodeName == utag || cj.nodeName == tagName  || tagName == '*'){
11200                         result[++ri] = cj;
11201                     }
11202                 }
11203             }
11204         // Immediately Preceding mode (+)
11205         // E + F all elements with the tag F that are immediately preceded by an element with the tag E
11206         }else if(mode == "+"){
11207             var utag = tagName.toUpperCase();
11208             for(var i = 0, n; n = ns[i]; i++){
11209                 while((n = n.nextSibling) && n.nodeType != 1);
11210                 if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){
11211                     result[++ri] = n;
11212                 }
11213             }
11214         // Sibling mode (~)
11215         // E ~ F all elements with the tag F that are preceded by a sibling element with the tag E
11216         }else if(mode == "~"){
11217             var utag = tagName.toUpperCase();
11218             for(var i = 0, n; n = ns[i]; i++){
11219                 while((n = n.nextSibling)){
11220                     if (n.nodeName == utag || n.nodeName == tagName || tagName == '*'){
11221                         result[++ri] = n;
11222                     }
11223                 }
11224             }
11225         }
11226         return result;
11227     }
11228
11229     function concat(a, b){
11230         if(b.slice){
11231             return a.concat(b);
11232         }
11233         for(var i = 0, l = b.length; i < l; i++){
11234             a[a.length] = b[i];
11235         }
11236         return a;
11237     }
11238
11239     function byTag(cs, tagName){
11240         if(cs.tagName || cs == document){
11241             cs = [cs];
11242         }
11243         if(!tagName){
11244             return cs;
11245         }
11246         var result = [], ri = -1;
11247         tagName = tagName.toLowerCase();
11248         for(var i = 0, ci; ci = cs[i]; i++){
11249             if(ci.nodeType == 1 && ci.tagName.toLowerCase() == tagName){
11250                 result[++ri] = ci;
11251             }
11252         }
11253         return result;
11254     }
11255
11256     function byId(cs, id){
11257         if(cs.tagName || cs == document){
11258             cs = [cs];
11259         }
11260         if(!id){
11261             return cs;
11262         }
11263         var result = [], ri = -1;
11264         for(var i = 0, ci; ci = cs[i]; i++){
11265             if(ci && ci.id == id){
11266                 result[++ri] = ci;
11267                 return result;
11268             }
11269         }
11270         return result;
11271     }
11272
11273     // operators are =, !=, ^=, $=, *=, %=, |= and ~=
11274     // custom can be "{"
11275     function byAttribute(cs, attr, value, op, custom){
11276         var result = [],
11277             ri = -1,
11278             useGetStyle = custom == "{",
11279             fn = Ext.DomQuery.operators[op],
11280             a,
11281             xml,
11282             hasXml;
11283
11284         for(var i = 0, ci; ci = cs[i]; i++){
11285             // skip non-element nodes.
11286             if(ci.nodeType != 1){
11287                 continue;
11288             }
11289             // only need to do this for the first node
11290             if(!hasXml){
11291                 xml = Ext.DomQuery.isXml(ci);
11292                 hasXml = true;
11293             }
11294
11295             // we only need to change the property names if we're dealing with html nodes, not XML
11296             if(!xml){
11297                 if(useGetStyle){
11298                     a = Ext.DomQuery.getStyle(ci, attr);
11299                 } else if (attr == "class" || attr == "className"){
11300                     a = ci.className;
11301                 } else if (attr == "for"){
11302                     a = ci.htmlFor;
11303                 } else if (attr == "href"){
11304                     // getAttribute href bug
11305                     // http://www.glennjones.net/Post/809/getAttributehrefbug.htm
11306                     a = ci.getAttribute("href", 2);
11307                 } else{
11308                     a = ci.getAttribute(attr);
11309                 }
11310             }else{
11311                 a = ci.getAttribute(attr);
11312             }
11313             if((fn && fn(a, value)) || (!fn && a)){
11314                 result[++ri] = ci;
11315             }
11316         }
11317         return result;
11318     }
11319
11320     function byPseudo(cs, name, value){
11321         return Ext.DomQuery.pseudos[name](cs, value);
11322     }
11323
11324     function nodupIEXml(cs){
11325         var d = ++key,
11326             r;
11327         cs[0].setAttribute("_nodup", d);
11328         r = [cs[0]];
11329         for(var i = 1, len = cs.length; i < len; i++){
11330             var c = cs[i];
11331             if(!c.getAttribute("_nodup") != d){
11332                 c.setAttribute("_nodup", d);
11333                 r[r.length] = c;
11334             }
11335         }
11336         for(var i = 0, len = cs.length; i < len; i++){
11337             cs[i].removeAttribute("_nodup");
11338         }
11339         return r;
11340     }
11341
11342     function nodup(cs){
11343         if(!cs){
11344             return [];
11345         }
11346         var len = cs.length, c, i, r = cs, cj, ri = -1;
11347         if(!len || typeof cs.nodeType != "undefined" || len == 1){
11348             return cs;
11349         }
11350         if(isIE && typeof cs[0].selectSingleNode != "undefined"){
11351             return nodupIEXml(cs);
11352         }
11353         var d = ++key;
11354         cs[0]._nodup = d;
11355         for(i = 1; c = cs[i]; i++){
11356             if(c._nodup != d){
11357                 c._nodup = d;
11358             }else{
11359                 r = [];
11360                 for(var j = 0; j < i; j++){
11361                     r[++ri] = cs[j];
11362                 }
11363                 for(j = i+1; cj = cs[j]; j++){
11364                     if(cj._nodup != d){
11365                         cj._nodup = d;
11366                         r[++ri] = cj;
11367                     }
11368                 }
11369                 return r;
11370             }
11371         }
11372         return r;
11373     }
11374
11375     function quickDiffIEXml(c1, c2){
11376         var d = ++key,
11377             r = [];
11378         for(var i = 0, len = c1.length; i < len; i++){
11379             c1[i].setAttribute("_qdiff", d);
11380         }
11381         for(var i = 0, len = c2.length; i < len; i++){
11382             if(c2[i].getAttribute("_qdiff") != d){
11383                 r[r.length] = c2[i];
11384             }
11385         }
11386         for(var i = 0, len = c1.length; i < len; i++){
11387            c1[i].removeAttribute("_qdiff");
11388         }
11389         return r;
11390     }
11391
11392     function quickDiff(c1, c2){
11393         var len1 = c1.length,
11394             d = ++key,
11395             r = [];
11396         if(!len1){
11397             return c2;
11398         }
11399         if(isIE && typeof c1[0].selectSingleNode != "undefined"){
11400             return quickDiffIEXml(c1, c2);
11401         }
11402         for(var i = 0; i < len1; i++){
11403             c1[i]._qdiff = d;
11404         }
11405         for(var i = 0, len = c2.length; i < len; i++){
11406             if(c2[i]._qdiff != d){
11407                 r[r.length] = c2[i];
11408             }
11409         }
11410         return r;
11411     }
11412
11413     function quickId(ns, mode, root, id){
11414         if(ns == root){
11415            var d = root.ownerDocument || root;
11416            return d.getElementById(id);
11417         }
11418         ns = getNodes(ns, mode, "*");
11419         return byId(ns, id);
11420     }
11421
11422     return {
11423         getStyle : function(el, name){
11424             return Ext.fly(el).getStyle(name);
11425         },
11426         /**
11427          * Compiles a selector/xpath query into a reusable function. The returned function
11428          * takes one parameter "root" (optional), which is the context node from where the query should start.
11429          * @param {String} selector The selector/xpath query
11430          * @param {String} type (optional) Either "select" (the default) or "simple" for a simple selector match
11431          * @return {Function}
11432          */
11433         compile : function(path, type){
11434             type = type || "select";
11435
11436             // setup fn preamble
11437             var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
11438                 mode,
11439                 lastPath,
11440                 matchers = Ext.DomQuery.matchers,
11441                 matchersLn = matchers.length,
11442                 modeMatch,
11443                 // accept leading mode switch
11444                 lmode = path.match(modeRe);
11445
11446             if(lmode && lmode[1]){
11447                 fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
11448                 path = path.replace(lmode[1], "");
11449             }
11450
11451             // strip leading slashes
11452             while(path.substr(0, 1)=="/"){
11453                 path = path.substr(1);
11454             }
11455
11456             while(path && lastPath != path){
11457                 lastPath = path;
11458                 var tokenMatch = path.match(tagTokenRe);
11459                 if(type == "select"){
11460                     if(tokenMatch){
11461                         // ID Selector
11462                         if(tokenMatch[1] == "#"){
11463                             fn[fn.length] = 'n = quickId(n, mode, root, "'+tokenMatch[2]+'");';
11464                         }else{
11465                             fn[fn.length] = 'n = getNodes(n, mode, "'+tokenMatch[2]+'");';
11466                         }
11467                         path = path.replace(tokenMatch[0], "");
11468                     }else if(path.substr(0, 1) != '@'){
11469                         fn[fn.length] = 'n = getNodes(n, mode, "*");';
11470                     }
11471                 // type of "simple"
11472                 }else{
11473                     if(tokenMatch){
11474                         if(tokenMatch[1] == "#"){
11475                             fn[fn.length] = 'n = byId(n, "'+tokenMatch[2]+'");';
11476                         }else{
11477                             fn[fn.length] = 'n = byTag(n, "'+tokenMatch[2]+'");';
11478                         }
11479                         path = path.replace(tokenMatch[0], "");
11480                     }
11481                 }
11482                 while(!(modeMatch = path.match(modeRe))){
11483                     var matched = false;
11484                     for(var j = 0; j < matchersLn; j++){
11485                         var t = matchers[j];
11486                         var m = path.match(t.re);
11487                         if(m){
11488                             fn[fn.length] = t.select.replace(tplRe, function(x, i){
11489                                 return m[i];
11490                             });
11491                             path = path.replace(m[0], "");
11492                             matched = true;
11493                             break;
11494                         }
11495                     }
11496                     // prevent infinite loop on bad selector
11497                     if(!matched){
11498                         Ext.Error.raise({
11499                             sourceClass: 'Ext.DomQuery',
11500                             sourceMethod: 'compile',
11501                             msg: 'Error parsing selector. Parsing failed at "' + path + '"'
11502                         });
11503                     }
11504                 }
11505                 if(modeMatch[1]){
11506                     fn[fn.length] = 'mode="'+modeMatch[1].replace(trimRe, "")+'";';
11507                     path = path.replace(modeMatch[1], "");
11508                 }
11509             }
11510             // close fn out
11511             fn[fn.length] = "return nodup(n);\n}";
11512
11513             // eval fn and return it
11514             eval(fn.join(""));
11515             return f;
11516         },
11517
11518         /**
11519          * Selects an array of DOM nodes using JavaScript-only implementation.
11520          *
11521          * Use {@link #select} to take advantage of browsers built-in support for CSS selectors.
11522          *
11523          * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
11524          * @param {Node/String} root (optional) The start of the query (defaults to document).
11525          * @return {Array} An Array of DOM elements which match the selector. If there are
11526          * no matches, and empty Array is returned.
11527          */
11528         jsSelect: function(path, root, type){
11529             // set root to doc if not specified.
11530             root = root || document;
11531
11532             if(typeof root == "string"){
11533                 root = document.getElementById(root);
11534             }
11535             var paths = path.split(","),
11536                 results = [];
11537
11538             // loop over each selector
11539             for(var i = 0, len = paths.length; i < len; i++){
11540                 var subPath = paths[i].replace(trimRe, "");
11541                 // compile and place in cache
11542                 if(!cache[subPath]){
11543                     cache[subPath] = Ext.DomQuery.compile(subPath);
11544                     if(!cache[subPath]){
11545                         Ext.Error.raise({
11546                             sourceClass: 'Ext.DomQuery',
11547                             sourceMethod: 'jsSelect',
11548                             msg: subPath + ' is not a valid selector'
11549                         });
11550                     }
11551                 }
11552                 var result = cache[subPath](root);
11553                 if(result && result != document){
11554                     results = results.concat(result);
11555                 }
11556             }
11557
11558             // if there were multiple selectors, make sure dups
11559             // are eliminated
11560             if(paths.length > 1){
11561                 return nodup(results);
11562             }
11563             return results;
11564         },
11565
11566         isXml: function(el) {
11567             var docEl = (el ? el.ownerDocument || el : 0).documentElement;
11568             return docEl ? docEl.nodeName !== "HTML" : false;
11569         },
11570
11571         /**
11572          * Selects an array of DOM nodes by CSS/XPath selector.
11573          *
11574          * Uses [document.querySelectorAll][0] if browser supports that, otherwise falls back to
11575          * {@link #jsSelect} to do the work.
11576          * 
11577          * Aliased as {@link Ext#query}.
11578          * 
11579          * [0]: https://developer.mozilla.org/en/DOM/document.querySelectorAll
11580          *
11581          * @param {String} path The selector/xpath query
11582          * @param {Node} root (optional) The start of the query (defaults to document).
11583          * @return {Array} An array of DOM elements (not a NodeList as returned by `querySelectorAll`).
11584          * Empty array when no matches.
11585          * @method
11586          */
11587         select : document.querySelectorAll ? function(path, root, type) {
11588             root = root || document;
11589             if (!Ext.DomQuery.isXml(root)) {
11590             try {
11591                 var cs = root.querySelectorAll(path);
11592                 return Ext.Array.toArray(cs);
11593             }
11594             catch (ex) {}
11595             }
11596             return Ext.DomQuery.jsSelect.call(this, path, root, type);
11597         } : function(path, root, type) {
11598             return Ext.DomQuery.jsSelect.call(this, path, root, type);
11599         },
11600
11601         /**
11602          * Selects a single element.
11603          * @param {String} selector The selector/xpath query
11604          * @param {Node} root (optional) The start of the query (defaults to document).
11605          * @return {Element} The DOM element which matched the selector.
11606          */
11607         selectNode : function(path, root){
11608             return Ext.DomQuery.select(path, root)[0];
11609         },
11610
11611         /**
11612          * Selects the value of a node, optionally replacing null with the defaultValue.
11613          * @param {String} selector The selector/xpath query
11614          * @param {Node} root (optional) The start of the query (defaults to document).
11615          * @param {String} defaultValue
11616          * @return {String}
11617          */
11618         selectValue : function(path, root, defaultValue){
11619             path = path.replace(trimRe, "");
11620             if(!valueCache[path]){
11621                 valueCache[path] = Ext.DomQuery.compile(path, "select");
11622             }
11623             var n = valueCache[path](root), v;
11624             n = n[0] ? n[0] : n;
11625
11626             // overcome a limitation of maximum textnode size
11627             // Rumored to potentially crash IE6 but has not been confirmed.
11628             // http://reference.sitepoint.com/javascript/Node/normalize
11629             // https://developer.mozilla.org/En/DOM/Node.normalize
11630             if (typeof n.normalize == 'function') n.normalize();
11631
11632             v = (n && n.firstChild ? n.firstChild.nodeValue : null);
11633             return ((v === null||v === undefined||v==='') ? defaultValue : v);
11634         },
11635
11636         /**
11637          * Selects the value of a node, parsing integers and floats. Returns the defaultValue, or 0 if none is specified.
11638          * @param {String} selector The selector/xpath query
11639          * @param {Node} root (optional) The start of the query (defaults to document).
11640          * @param {Number} defaultValue
11641          * @return {Number}
11642          */
11643         selectNumber : function(path, root, defaultValue){
11644             var v = Ext.DomQuery.selectValue(path, root, defaultValue || 0);
11645             return parseFloat(v);
11646         },
11647
11648         /**
11649          * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
11650          * @param {String/HTMLElement/Array} el An element id, element or array of elements
11651          * @param {String} selector The simple selector to test
11652          * @return {Boolean}
11653          */
11654         is : function(el, ss){
11655             if(typeof el == "string"){
11656                 el = document.getElementById(el);
11657             }
11658             var isArray = Ext.isArray(el),
11659                 result = Ext.DomQuery.filter(isArray ? el : [el], ss);
11660             return isArray ? (result.length == el.length) : (result.length > 0);
11661         },
11662
11663         /**
11664          * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
11665          * @param {Array} el An array of elements to filter
11666          * @param {String} selector The simple selector to test
11667          * @param {Boolean} nonMatches If true, it returns the elements that DON'T match
11668          * the selector instead of the ones that match
11669          * @return {Array} An Array of DOM elements which match the selector. If there are
11670          * no matches, and empty Array is returned.
11671          */
11672         filter : function(els, ss, nonMatches){
11673             ss = ss.replace(trimRe, "");
11674             if(!simpleCache[ss]){
11675                 simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");
11676             }
11677             var result = simpleCache[ss](els);
11678             return nonMatches ? quickDiff(result, els) : result;
11679         },
11680
11681         /**
11682          * Collection of matching regular expressions and code snippets.
11683          * Each capture group within () will be replace the {} in the select
11684          * statement as specified by their index.
11685          */
11686         matchers : [{
11687                 re: /^\.([\w-]+)/,
11688                 select: 'n = byClassName(n, " {1} ");'
11689             }, {
11690                 re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
11691                 select: 'n = byPseudo(n, "{1}", "{2}");'
11692             },{
11693                 re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
11694                 select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
11695             }, {
11696                 re: /^#([\w-]+)/,
11697                 select: 'n = byId(n, "{1}");'
11698             },{
11699                 re: /^@([\w-]+)/,
11700                 select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
11701             }
11702         ],
11703
11704         /**
11705          * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.
11706          * 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;.
11707          */
11708         operators : {
11709             "=" : function(a, v){
11710                 return a == v;
11711             },
11712             "!=" : function(a, v){
11713                 return a != v;
11714             },
11715             "^=" : function(a, v){
11716                 return a && a.substr(0, v.length) == v;
11717             },
11718             "$=" : function(a, v){
11719                 return a && a.substr(a.length-v.length) == v;
11720             },
11721             "*=" : function(a, v){
11722                 return a && a.indexOf(v) !== -1;
11723             },
11724             "%=" : function(a, v){
11725                 return (a % v) == 0;
11726             },
11727             "|=" : function(a, v){
11728                 return a && (a == v || a.substr(0, v.length+1) == v+'-');
11729             },
11730             "~=" : function(a, v){
11731                 return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
11732             }
11733         },
11734
11735         /**
11736 Object hash of "pseudo class" filter functions which are used when filtering selections. 
11737 Each function is passed two parameters:
11738
11739 - **c** : Array
11740     An Array of DOM elements to filter.
11741     
11742 - **v** : String
11743     The argument (if any) supplied in the selector.
11744
11745 A filter function returns an Array of DOM elements which conform to the pseudo class.
11746 In addition to the provided pseudo classes listed above such as `first-child` and `nth-child`,
11747 developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.
11748
11749 For example, to filter `a` elements to only return links to __external__ resources:
11750
11751     Ext.DomQuery.pseudos.external = function(c, v){
11752         var r = [], ri = -1;
11753         for(var i = 0, ci; ci = c[i]; i++){
11754             // Include in result set only if it's a link to an external resource
11755             if(ci.hostname != location.hostname){
11756                 r[++ri] = ci;
11757             }
11758         }
11759         return r;
11760     };
11761
11762 Then external links could be gathered with the following statement:
11763
11764     var externalLinks = Ext.select("a:external");
11765
11766         * @markdown
11767         */
11768         pseudos : {
11769             "first-child" : function(c){
11770                 var r = [], ri = -1, n;
11771                 for(var i = 0, ci; ci = n = c[i]; i++){
11772                     while((n = n.previousSibling) && n.nodeType != 1);
11773                     if(!n){
11774                         r[++ri] = ci;
11775                     }
11776                 }
11777                 return r;
11778             },
11779
11780             "last-child" : function(c){
11781                 var r = [], ri = -1, n;
11782                 for(var i = 0, ci; ci = n = c[i]; i++){
11783                     while((n = n.nextSibling) && n.nodeType != 1);
11784                     if(!n){
11785                         r[++ri] = ci;
11786                     }
11787                 }
11788                 return r;
11789             },
11790
11791             "nth-child" : function(c, a) {
11792                 var r = [], ri = -1,
11793                     m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
11794                     f = (m[1] || 1) - 0, l = m[2] - 0;
11795                 for(var i = 0, n; n = c[i]; i++){
11796                     var pn = n.parentNode;
11797                     if (batch != pn._batch) {
11798                         var j = 0;
11799                         for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
11800                             if(cn.nodeType == 1){
11801                                cn.nodeIndex = ++j;
11802                             }
11803                         }
11804                         pn._batch = batch;
11805                     }
11806                     if (f == 1) {
11807                         if (l == 0 || n.nodeIndex == l){
11808                             r[++ri] = n;
11809                         }
11810                     } else if ((n.nodeIndex + l) % f == 0){
11811                         r[++ri] = n;
11812                     }
11813                 }
11814
11815                 return r;
11816             },
11817
11818             "only-child" : function(c){
11819                 var r = [], ri = -1;;
11820                 for(var i = 0, ci; ci = c[i]; i++){
11821                     if(!prev(ci) && !next(ci)){
11822                         r[++ri] = ci;
11823                     }
11824                 }
11825                 return r;
11826             },
11827
11828             "empty" : function(c){
11829                 var r = [], ri = -1;
11830                 for(var i = 0, ci; ci = c[i]; i++){
11831                     var cns = ci.childNodes, j = 0, cn, empty = true;
11832                     while(cn = cns[j]){
11833                         ++j;
11834                         if(cn.nodeType == 1 || cn.nodeType == 3){
11835                             empty = false;
11836                             break;
11837                         }
11838                     }
11839                     if(empty){
11840                         r[++ri] = ci;
11841                     }
11842                 }
11843                 return r;
11844             },
11845
11846             "contains" : function(c, v){
11847                 var r = [], ri = -1;
11848                 for(var i = 0, ci; ci = c[i]; i++){
11849                     if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
11850                         r[++ri] = ci;
11851                     }
11852                 }
11853                 return r;
11854             },
11855
11856             "nodeValue" : function(c, v){
11857                 var r = [], ri = -1;
11858                 for(var i = 0, ci; ci = c[i]; i++){
11859                     if(ci.firstChild && ci.firstChild.nodeValue == v){
11860                         r[++ri] = ci;
11861                     }
11862                 }
11863                 return r;
11864             },
11865
11866             "checked" : function(c){
11867                 var r = [], ri = -1;
11868                 for(var i = 0, ci; ci = c[i]; i++){
11869                     if(ci.checked == true){
11870                         r[++ri] = ci;
11871                     }
11872                 }
11873                 return r;
11874             },
11875
11876             "not" : function(c, ss){
11877                 return Ext.DomQuery.filter(c, ss, true);
11878             },
11879
11880             "any" : function(c, selectors){
11881                 var ss = selectors.split('|'),
11882                     r = [], ri = -1, s;
11883                 for(var i = 0, ci; ci = c[i]; i++){
11884                     for(var j = 0; s = ss[j]; j++){
11885                         if(Ext.DomQuery.is(ci, s)){
11886                             r[++ri] = ci;
11887                             break;
11888                         }
11889                     }
11890                 }
11891                 return r;
11892             },
11893
11894             "odd" : function(c){
11895                 return this["nth-child"](c, "odd");
11896             },
11897
11898             "even" : function(c){
11899                 return this["nth-child"](c, "even");
11900             },
11901
11902             "nth" : function(c, a){
11903                 return c[a-1] || [];
11904             },
11905
11906             "first" : function(c){
11907                 return c[0] || [];
11908             },
11909
11910             "last" : function(c){
11911                 return c[c.length-1] || [];
11912             },
11913
11914             "has" : function(c, ss){
11915                 var s = Ext.DomQuery.select,
11916                     r = [], ri = -1;
11917                 for(var i = 0, ci; ci = c[i]; i++){
11918                     if(s(ss, ci).length > 0){
11919                         r[++ri] = ci;
11920                     }
11921                 }
11922                 return r;
11923             },
11924
11925             "next" : function(c, ss){
11926                 var is = Ext.DomQuery.is,
11927                     r = [], ri = -1;
11928                 for(var i = 0, ci; ci = c[i]; i++){
11929                     var n = next(ci);
11930                     if(n && is(n, ss)){
11931                         r[++ri] = ci;
11932                     }
11933                 }
11934                 return r;
11935             },
11936
11937             "prev" : function(c, ss){
11938                 var is = Ext.DomQuery.is,
11939                     r = [], ri = -1;
11940                 for(var i = 0, ci; ci = c[i]; i++){
11941                     var n = prev(ci);
11942                     if(n && is(n, ss)){
11943                         r[++ri] = ci;
11944                     }
11945                 }
11946                 return r;
11947             }
11948         }
11949     };
11950 }();
11951
11952 /**
11953  * Selects an array of DOM nodes by CSS/XPath selector. Shorthand of {@link Ext.DomQuery#select}
11954  * @param {String} path The selector/xpath query
11955  * @param {Node} root (optional) The start of the query (defaults to document).
11956  * @return {Array}
11957  * @member Ext
11958  * @method query
11959  */
11960 Ext.query = Ext.DomQuery.select;
11961
11962 /**
11963  * @class Ext.core.Element
11964  * <p>Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.</p>
11965  * <p>All instances of this class inherit the methods of {@link Ext.fx.Anim} making visual effects easily available to all DOM elements.</p>
11966  * <p>Note that the events documented in this class are not Ext events, they encapsulate browser events. To
11967  * access the underlying browser event, see {@link Ext.EventObject#browserEvent}. Some older
11968  * browsers may not support the full range of events. Which events are supported is beyond the control of ExtJs.</p>
11969  * Usage:<br>
11970 <pre><code>
11971 // by id
11972 var el = Ext.get("my-div");
11973
11974 // by DOM element reference
11975 var el = Ext.get(myDivElement);
11976 </code></pre>
11977  * <b>Animations</b><br />
11978  * <p>When an element is manipulated, by default there is no animation.</p>
11979  * <pre><code>
11980 var el = Ext.get("my-div");
11981
11982 // no animation
11983 el.setWidth(100);
11984  * </code></pre>
11985  * <p>Many of the functions for manipulating an element have an optional "animate" parameter.  This
11986  * parameter can be specified as boolean (<tt>true</tt>) for default animation effects.</p>
11987  * <pre><code>
11988 // default animation
11989 el.setWidth(100, true);
11990  * </code></pre>
11991  *
11992  * <p>To configure the effects, an object literal with animation options to use as the Element animation
11993  * configuration object can also be specified. Note that the supported Element animation configuration
11994  * options are a subset of the {@link Ext.fx.Anim} animation options specific to Fx effects.  The supported
11995  * Element animation configuration options are:</p>
11996 <pre>
11997 Option    Default   Description
11998 --------- --------  ---------------------------------------------
11999 {@link Ext.fx.Anim#duration duration}  .35       The duration of the animation in seconds
12000 {@link Ext.fx.Anim#easing easing}    easeOut   The easing method
12001 {@link Ext.fx.Anim#callback callback}  none      A function to execute when the anim completes
12002 {@link Ext.fx.Anim#scope scope}     this      The scope (this) of the callback function
12003 </pre>
12004  *
12005  * <pre><code>
12006 // Element animation options object
12007 var opt = {
12008     {@link Ext.fx.Anim#duration duration}: 1,
12009     {@link Ext.fx.Anim#easing easing}: 'elasticIn',
12010     {@link Ext.fx.Anim#callback callback}: this.foo,
12011     {@link Ext.fx.Anim#scope scope}: this
12012 };
12013 // animation with some options set
12014 el.setWidth(100, opt);
12015  * </code></pre>
12016  * <p>The Element animation object being used for the animation will be set on the options
12017  * object as "anim", which allows you to stop or manipulate the animation. Here is an example:</p>
12018  * <pre><code>
12019 // using the "anim" property to get the Anim object
12020 if(opt.anim.isAnimated()){
12021     opt.anim.stop();
12022 }
12023  * </code></pre>
12024  * <p>Also see the <tt>{@link #animate}</tt> method for another animation technique.</p>
12025  * <p><b> Composite (Collections of) Elements</b></p>
12026  * <p>For working with collections of Elements, see {@link Ext.CompositeElement}</p>
12027  * @constructor Create a new Element directly.
12028  * @param {String/HTMLElement} element
12029  * @param {Boolean} forceNew (optional) By default the constructor checks to see if there is already an instance of this element in the cache and if there is it returns the same instance. This will skip that check (useful for extending this class).
12030  */
12031  (function() {
12032     var DOC = document,
12033         EC = Ext.cache;
12034
12035     Ext.Element = Ext.core.Element = function(element, forceNew) {
12036         var dom = typeof element == "string" ? DOC.getElementById(element) : element,
12037         id;
12038
12039         if (!dom) {
12040             return null;
12041         }
12042
12043         id = dom.id;
12044
12045         if (!forceNew && id && EC[id]) {
12046             // element object already exists
12047             return EC[id].el;
12048         }
12049
12050         /**
12051      * The DOM element
12052      * @type HTMLElement
12053      */
12054         this.dom = dom;
12055
12056         /**
12057      * The DOM element ID
12058      * @type String
12059      */
12060         this.id = id || Ext.id(dom);
12061     };
12062
12063     var DH = Ext.core.DomHelper,
12064     El = Ext.core.Element;
12065
12066
12067     El.prototype = {
12068         /**
12069      * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
12070      * @param {Object} o The object with the attributes
12071      * @param {Boolean} useSet (optional) false to override the default setAttribute to use expandos.
12072      * @return {Ext.core.Element} this
12073      */
12074         set: function(o, useSet) {
12075             var el = this.dom,
12076                 attr,
12077                 val;
12078             useSet = (useSet !== false) && !!el.setAttribute;
12079
12080             for (attr in o) {
12081                 if (o.hasOwnProperty(attr)) {
12082                     val = o[attr];
12083                     if (attr == 'style') {
12084                         DH.applyStyles(el, val);
12085                     } else if (attr == 'cls') {
12086                         el.className = val;
12087                     } else if (useSet) {
12088                         el.setAttribute(attr, val);
12089                     } else {
12090                         el[attr] = val;
12091                     }
12092                 }
12093             }
12094             return this;
12095         },
12096
12097         //  Mouse events
12098         /**
12099      * @event click
12100      * Fires when a mouse click is detected within the element.
12101      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12102      * @param {HtmlElement} t The target of the event.
12103      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12104      */
12105         /**
12106      * @event contextmenu
12107      * Fires when a right click is detected within the element.
12108      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12109      * @param {HtmlElement} t The target of the event.
12110      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12111      */
12112         /**
12113      * @event dblclick
12114      * Fires when a mouse double click is detected within the element.
12115      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12116      * @param {HtmlElement} t The target of the event.
12117      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12118      */
12119         /**
12120      * @event mousedown
12121      * Fires when a mousedown is detected within the element.
12122      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12123      * @param {HtmlElement} t The target of the event.
12124      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12125      */
12126         /**
12127      * @event mouseup
12128      * Fires when a mouseup is detected within the element.
12129      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12130      * @param {HtmlElement} t The target of the event.
12131      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12132      */
12133         /**
12134      * @event mouseover
12135      * Fires when a mouseover is detected within the element.
12136      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12137      * @param {HtmlElement} t The target of the event.
12138      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12139      */
12140         /**
12141      * @event mousemove
12142      * Fires when a mousemove is detected with the element.
12143      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12144      * @param {HtmlElement} t The target of the event.
12145      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12146      */
12147         /**
12148      * @event mouseout
12149      * Fires when a mouseout is detected with the element.
12150      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12151      * @param {HtmlElement} t The target of the event.
12152      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12153      */
12154         /**
12155      * @event mouseenter
12156      * Fires when the mouse enters the element.
12157      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12158      * @param {HtmlElement} t The target of the event.
12159      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12160      */
12161         /**
12162      * @event mouseleave
12163      * Fires when the mouse leaves the element.
12164      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12165      * @param {HtmlElement} t The target of the event.
12166      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12167      */
12168
12169         //  Keyboard events
12170         /**
12171      * @event keypress
12172      * Fires when a keypress is detected within the element.
12173      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12174      * @param {HtmlElement} t The target of the event.
12175      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12176      */
12177         /**
12178      * @event keydown
12179      * Fires when a keydown is detected within the element.
12180      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12181      * @param {HtmlElement} t The target of the event.
12182      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12183      */
12184         /**
12185      * @event keyup
12186      * Fires when a keyup is detected within the element.
12187      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12188      * @param {HtmlElement} t The target of the event.
12189      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12190      */
12191
12192
12193         //  HTML frame/object events
12194         /**
12195      * @event load
12196      * Fires when the user agent finishes loading all content within the element. Only supported by window, frames, objects and images.
12197      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12198      * @param {HtmlElement} t The target of the event.
12199      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12200      */
12201         /**
12202      * @event unload
12203      * Fires when the user agent removes all content from a window or frame. For elements, it fires when the target element or any of its content has been removed.
12204      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12205      * @param {HtmlElement} t The target of the event.
12206      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12207      */
12208         /**
12209      * @event abort
12210      * Fires when an object/image is stopped from loading before completely loaded.
12211      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12212      * @param {HtmlElement} t The target of the event.
12213      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12214      */
12215         /**
12216      * @event error
12217      * Fires when an object/image/frame cannot be loaded properly.
12218      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12219      * @param {HtmlElement} t The target of the event.
12220      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12221      */
12222         /**
12223      * @event resize
12224      * Fires when a document view is resized.
12225      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12226      * @param {HtmlElement} t The target of the event.
12227      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12228      */
12229         /**
12230      * @event scroll
12231      * Fires when a document view is scrolled.
12232      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12233      * @param {HtmlElement} t The target of the event.
12234      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12235      */
12236
12237         //  Form events
12238         /**
12239      * @event select
12240      * Fires when a user selects some text in a text field, including input and textarea.
12241      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12242      * @param {HtmlElement} t The target of the event.
12243      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12244      */
12245         /**
12246      * @event change
12247      * Fires when a control loses the input focus and its value has been modified since gaining focus.
12248      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12249      * @param {HtmlElement} t The target of the event.
12250      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12251      */
12252         /**
12253      * @event submit
12254      * Fires when a form is submitted.
12255      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12256      * @param {HtmlElement} t The target of the event.
12257      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12258      */
12259         /**
12260      * @event reset
12261      * Fires when a form is reset.
12262      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12263      * @param {HtmlElement} t The target of the event.
12264      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12265      */
12266         /**
12267      * @event focus
12268      * Fires when an element receives focus either via the pointing device or by tab navigation.
12269      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12270      * @param {HtmlElement} t The target of the event.
12271      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12272      */
12273         /**
12274      * @event blur
12275      * Fires when an element loses focus either via the pointing device or by tabbing navigation.
12276      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12277      * @param {HtmlElement} t The target of the event.
12278      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12279      */
12280
12281         //  User Interface events
12282         /**
12283      * @event DOMFocusIn
12284      * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
12285      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12286      * @param {HtmlElement} t The target of the event.
12287      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12288      */
12289         /**
12290      * @event DOMFocusOut
12291      * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
12292      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12293      * @param {HtmlElement} t The target of the event.
12294      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12295      */
12296         /**
12297      * @event DOMActivate
12298      * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
12299      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12300      * @param {HtmlElement} t The target of the event.
12301      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12302      */
12303
12304         //  DOM Mutation events
12305         /**
12306      * @event DOMSubtreeModified
12307      * Where supported. Fires when the subtree is modified.
12308      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12309      * @param {HtmlElement} t The target of the event.
12310      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12311      */
12312         /**
12313      * @event DOMNodeInserted
12314      * Where supported. Fires when a node has been added as a child of another node.
12315      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12316      * @param {HtmlElement} t The target of the event.
12317      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12318      */
12319         /**
12320      * @event DOMNodeRemoved
12321      * Where supported. Fires when a descendant node of the element is removed.
12322      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12323      * @param {HtmlElement} t The target of the event.
12324      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12325      */
12326         /**
12327      * @event DOMNodeRemovedFromDocument
12328      * Where supported. Fires when a node is being removed from a document.
12329      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12330      * @param {HtmlElement} t The target of the event.
12331      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12332      */
12333         /**
12334      * @event DOMNodeInsertedIntoDocument
12335      * Where supported. Fires when a node is being inserted into a document.
12336      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12337      * @param {HtmlElement} t The target of the event.
12338      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12339      */
12340         /**
12341      * @event DOMAttrModified
12342      * Where supported. Fires when an attribute has been modified.
12343      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12344      * @param {HtmlElement} t The target of the event.
12345      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12346      */
12347         /**
12348      * @event DOMCharacterDataModified
12349      * Where supported. Fires when the character data has been modified.
12350      * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
12351      * @param {HtmlElement} t The target of the event.
12352      * @param {Object} o The options configuration passed to the {@link #addListener} call.
12353      */
12354
12355         /**
12356      * The default unit to append to CSS values where a unit isn't provided (defaults to px).
12357      * @type String
12358      */
12359         defaultUnit: "px",
12360
12361         /**
12362      * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
12363      * @param {String} selector The simple selector to test
12364      * @return {Boolean} True if this element matches the selector, else false
12365      */
12366         is: function(simpleSelector) {
12367             return Ext.DomQuery.is(this.dom, simpleSelector);
12368         },
12369
12370         /**
12371      * Tries to focus the element. Any exceptions are caught and ignored.
12372      * @param {Number} defer (optional) Milliseconds to defer the focus
12373      * @return {Ext.core.Element} this
12374      */
12375         focus: function(defer,
12376                         /* private */
12377                         dom) {
12378             var me = this;
12379             dom = dom || me.dom;
12380             try {
12381                 if (Number(defer)) {
12382                     Ext.defer(me.focus, defer, null, [null, dom]);
12383                 } else {
12384                     dom.focus();
12385                 }
12386             } catch(e) {}
12387             return me;
12388         },
12389
12390         /**
12391      * Tries to blur the element. Any exceptions are caught and ignored.
12392      * @return {Ext.core.Element} this
12393      */
12394         blur: function() {
12395             try {
12396                 this.dom.blur();
12397             } catch(e) {}
12398             return this;
12399         },
12400
12401         /**
12402      * Returns the value of the "value" attribute
12403      * @param {Boolean} asNumber true to parse the value as a number
12404      * @return {String/Number}
12405      */
12406         getValue: function(asNumber) {
12407             var val = this.dom.value;
12408             return asNumber ? parseInt(val, 10) : val;
12409         },
12410
12411         /**
12412      * Appends an event handler to this element.  The shorthand version {@link #on} is equivalent.
12413      * @param {String} eventName The name of event to handle.
12414      * @param {Function} fn The handler function the event invokes. This function is passed
12415      * the following parameters:<ul>
12416      * <li><b>evt</b> : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
12417      * <li><b>el</b> : HtmlElement<div class="sub-desc">The DOM element which was the target of the event.
12418      * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
12419      * <li><b>o</b> : Object<div class="sub-desc">The options object from the addListener call.</div></li>
12420      * </ul>
12421      * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
12422      * <b>If omitted, defaults to this Element.</b>.
12423      * @param {Object} options (optional) An object containing handler configuration properties.
12424      * This may contain any of the following properties:<ul>
12425      * <li><b>scope</b> Object : <div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
12426      * <b>If omitted, defaults to this Element.</b></div></li>
12427      * <li><b>delegate</b> String: <div class="sub-desc">A simple selector to filter the target or look for a descendant of the target. See below for additional details.</div></li>
12428      * <li><b>stopEvent</b> Boolean: <div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
12429      * <li><b>preventDefault</b> Boolean: <div class="sub-desc">True to prevent the default action</div></li>
12430      * <li><b>stopPropagation</b> Boolean: <div class="sub-desc">True to prevent event propagation</div></li>
12431      * <li><b>normalized</b> Boolean: <div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
12432      * <li><b>target</b> Ext.core.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>
12433      * <li><b>delay</b> Number: <div class="sub-desc">The number of milliseconds to delay the invocation of the handler after the event fires.</div></li>
12434      * <li><b>single</b> 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>
12435      * <li><b>buffer</b> Number: <div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
12436      * by the specified number of milliseconds. If the event fires again within that time, the original
12437      * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
12438      * </ul><br>
12439      * <p>
12440      * <b>Combining Options</b><br>
12441      * In the following examples, the shorthand form {@link #on} is used rather than the more verbose
12442      * addListener.  The two are equivalent.  Using the options argument, it is possible to combine different
12443      * types of listeners:<br>
12444      * <br>
12445      * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the
12446      * options object. The options object is available as the third parameter in the handler function.<div style="margin: 5px 20px 20px;">
12447      * Code:<pre><code>
12448 el.on('click', this.onClick, this, {
12449     single: true,
12450     delay: 100,
12451     stopEvent : true,
12452     forumId: 4
12453 });</code></pre></p>
12454      * <p>
12455      * <b>Attaching multiple handlers in 1 call</b><br>
12456      * The method also allows for a single argument to be passed which is a config object containing properties
12457      * which specify multiple handlers.</p>
12458      * <p>
12459      * Code:<pre><code>
12460 el.on({
12461     'click' : {
12462         fn: this.onClick,
12463         scope: this,
12464         delay: 100
12465     },
12466     'mouseover' : {
12467         fn: this.onMouseOver,
12468         scope: this
12469     },
12470     'mouseout' : {
12471         fn: this.onMouseOut,
12472         scope: this
12473     }
12474 });</code></pre>
12475      * <p>
12476      * Or a shorthand syntax:<br>
12477      * Code:<pre><code></p>
12478 el.on({
12479     'click' : this.onClick,
12480     'mouseover' : this.onMouseOver,
12481     'mouseout' : this.onMouseOut,
12482     scope: this
12483 });
12484      * </code></pre></p>
12485      * <p><b>delegate</b></p>
12486      * <p>This is a configuration option that you can pass along when registering a handler for
12487      * an event to assist with event delegation. Event delegation is a technique that is used to
12488      * reduce memory consumption and prevent exposure to memory-leaks. By registering an event
12489      * for a container element as opposed to each element within a container. By setting this
12490      * configuration option to a simple selector, the target element will be filtered to look for
12491      * a descendant of the target.
12492      * For example:<pre><code>
12493 // using this markup:
12494 &lt;div id='elId'>
12495     &lt;p id='p1'>paragraph one&lt;/p>
12496     &lt;p id='p2' class='clickable'>paragraph two&lt;/p>
12497     &lt;p id='p3'>paragraph three&lt;/p>
12498 &lt;/div>
12499 // utilize event delegation to registering just one handler on the container element:
12500 el = Ext.get('elId');
12501 el.on(
12502     'click',
12503     function(e,t) {
12504         // handle click
12505         console.info(t.id); // 'p2'
12506     },
12507     this,
12508     {
12509         // filter the target element to be a descendant with the class 'clickable'
12510         delegate: '.clickable'
12511     }
12512 );
12513      * </code></pre></p>
12514      * @return {Ext.core.Element} this
12515      */
12516         addListener: function(eventName, fn, scope, options) {
12517             Ext.EventManager.on(this.dom, eventName, fn, scope || this, options);
12518             return this;
12519         },
12520
12521         /**
12522      * Removes an event handler from this element.  The shorthand version {@link #un} is equivalent.
12523      * <b>Note</b>: if a <i>scope</i> was explicitly specified when {@link #addListener adding} the
12524      * listener, the same scope must be specified here.
12525      * Example:
12526      * <pre><code>
12527 el.removeListener('click', this.handlerFn);
12528 // or
12529 el.un('click', this.handlerFn);
12530 </code></pre>
12531      * @param {String} eventName The name of the event from which to remove the handler.
12532      * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
12533      * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
12534      * then this must refer to the same object.
12535      * @return {Ext.core.Element} this
12536      */
12537         removeListener: function(eventName, fn, scope) {
12538             Ext.EventManager.un(this.dom, eventName, fn, scope || this);
12539             return this;
12540         },
12541
12542         /**
12543      * Removes all previous added listeners from this element
12544      * @return {Ext.core.Element} this
12545      */
12546         removeAllListeners: function() {
12547             Ext.EventManager.removeAll(this.dom);
12548             return this;
12549         },
12550
12551         /**
12552          * Recursively removes all previous added listeners from this element and its children
12553          * @return {Ext.core.Element} this
12554          */
12555         purgeAllListeners: function() {
12556             Ext.EventManager.purgeElement(this);
12557             return this;
12558         },
12559
12560         /**
12561          * @private Test if size has a unit, otherwise appends the passed unit string, or the default for this Element.
12562          * @param size {Mixed} The size to set
12563          * @param units {String} The units to append to a numeric size value
12564          */
12565         addUnits: function(size, units) {
12566
12567             // Most common case first: Size is set to a number
12568             if (Ext.isNumber(size)) {
12569                 return size + (units || this.defaultUnit || 'px');
12570             }
12571
12572             // Size set to a value which means "auto"
12573             if (size === "" || size == "auto" || size === undefined || size === null) {
12574                 return size || '';
12575             }
12576
12577             // Otherwise, warn if it's not a valid CSS measurement
12578             if (!unitPattern.test(size)) {
12579                 if (Ext.isDefined(Ext.global.console)) {
12580                     Ext.global.console.warn("Warning, size detected as NaN on Element.addUnits.");
12581                 }
12582                 return size || '';
12583             }
12584             return size;
12585         },
12586
12587         /**
12588          * Tests various css rules/browsers to determine if this element uses a border box
12589          * @return {Boolean}
12590          */
12591         isBorderBox: function() {
12592             return Ext.isBorderBox || noBoxAdjust[(this.dom.tagName || "").toLowerCase()];
12593         },
12594
12595         /**
12596          * <p>Removes this element's dom reference.  Note that event and cache removal is handled at {@link Ext#removeNode Ext.removeNode}</p>
12597          */
12598         remove: function() {
12599             var me = this,
12600             dom = me.dom;
12601
12602             if (dom) {
12603                 delete me.dom;
12604                 Ext.removeNode(dom);
12605             }
12606         },
12607
12608         /**
12609          * Sets up event handlers to call the passed functions when the mouse is moved into and out of the Element.
12610          * @param {Function} overFn The function to call when the mouse enters the Element.
12611          * @param {Function} outFn The function to call when the mouse leaves the Element.
12612          * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the functions are executed. Defaults to the Element's DOM element.
12613          * @param {Object} options (optional) Options for the listener. See {@link Ext.util.Observable#addListener the <tt>options</tt> parameter}.
12614          * @return {Ext.core.Element} this
12615          */
12616         hover: function(overFn, outFn, scope, options) {
12617             var me = this;
12618             me.on('mouseenter', overFn, scope || me.dom, options);
12619             me.on('mouseleave', outFn, scope || me.dom, options);
12620             return me;
12621         },
12622
12623         /**
12624          * Returns true if this element is an ancestor of the passed element
12625          * @param {HTMLElement/String} el The element to check
12626          * @return {Boolean} True if this element is an ancestor of el, else false
12627          */
12628         contains: function(el) {
12629             return ! el ? false: Ext.core.Element.isAncestor(this.dom, el.dom ? el.dom: el);
12630         },
12631
12632         /**
12633          * Returns the value of a namespaced attribute from the element's underlying DOM node.
12634          * @param {String} namespace The namespace in which to look for the attribute
12635          * @param {String} name The attribute name
12636          * @return {String} The attribute value
12637          * @deprecated
12638          */
12639         getAttributeNS: function(ns, name) {
12640             return this.getAttribute(name, ns);
12641         },
12642
12643         /**
12644          * Returns the value of an attribute from the element's underlying DOM node.
12645          * @param {String} name The attribute name
12646          * @param {String} namespace (optional) The namespace in which to look for the attribute
12647          * @return {String} The attribute value
12648          * @method
12649          */
12650         getAttribute: (Ext.isIE && !(Ext.isIE9 && document.documentMode === 9)) ?
12651         function(name, ns) {
12652             var d = this.dom,
12653             type;
12654             if(ns) {
12655                 type = typeof d[ns + ":" + name];
12656                 if (type != 'undefined' && type != 'unknown') {
12657                     return d[ns + ":" + name] || null;
12658                 }
12659                 return null;
12660             }
12661             if (name === "for") {
12662                 name = "htmlFor";
12663             }
12664             return d[name] || null;
12665         }: function(name, ns) {
12666             var d = this.dom;
12667             if (ns) {
12668                return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name);
12669             }
12670             return  d.getAttribute(name) || d[name] || null;
12671         },
12672
12673         /**
12674          * Update the innerHTML of this element
12675          * @param {String} html The new HTML
12676          * @return {Ext.core.Element} this
12677          */
12678         update: function(html) {
12679             if (this.dom) {
12680                 this.dom.innerHTML = html;
12681             }
12682             return this;
12683         }
12684     };
12685
12686     var ep = El.prototype;
12687
12688     El.addMethods = function(o) {
12689         Ext.apply(ep, o);
12690     };
12691
12692     /**
12693      * Appends an event handler (shorthand for {@link #addListener}).
12694      * @param {String} eventName The name of event to handle.
12695      * @param {Function} fn The handler function the event invokes.
12696      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function is executed.
12697      * @param {Object} options (optional) An object containing standard {@link #addListener} options
12698      * @member Ext.core.Element
12699      * @method on
12700      */
12701     ep.on = ep.addListener;
12702
12703     /**
12704      * Removes an event handler from this element (see {@link #removeListener} for additional notes).
12705      * @param {String} eventName The name of the event from which to remove the handler.
12706      * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
12707      * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
12708      * then this must refer to the same object.
12709      * @return {Ext.core.Element} this
12710      * @member Ext.core.Element
12711      * @method un
12712      */
12713     ep.un = ep.removeListener;
12714
12715     /**
12716      * Removes all previous added listeners from this element
12717      * @return {Ext.core.Element} this
12718      * @member Ext.core.Element
12719      * @method clearListeners
12720      */
12721     ep.clearListeners = ep.removeAllListeners;
12722
12723     /**
12724      * Removes this element's dom reference.  Note that event and cache removal is handled at {@link Ext#removeNode Ext.removeNode}.
12725      * Alias to {@link #remove}.
12726      * @member Ext.core.Element
12727      * @method destroy
12728      */
12729     ep.destroy = ep.remove;
12730
12731     /**
12732      * true to automatically adjust width and height settings for box-model issues (default to true)
12733      */
12734     ep.autoBoxAdjust = true;
12735
12736     // private
12737     var unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
12738     docEl;
12739
12740     /**
12741      * Retrieves Ext.core.Element objects.
12742      * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
12743      * retrieves Ext.core.Element objects which encapsulate DOM elements. To retrieve a Component by
12744      * its ID, use {@link Ext.ComponentManager#get}.</p>
12745      * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
12746      * object was recreated with the same id via AJAX or DOM.</p>
12747      * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
12748      * @return {Element} The Element object (or null if no matching element was found)
12749      * @static
12750      * @member Ext.core.Element
12751      * @method get
12752      */
12753     El.get = function(el) {
12754         var ex,
12755         elm,
12756         id;
12757         if (!el) {
12758             return null;
12759         }
12760         if (typeof el == "string") {
12761             // element id
12762             if (! (elm = DOC.getElementById(el))) {
12763                 return null;
12764             }
12765             if (EC[el] && EC[el].el) {
12766                 ex = EC[el].el;
12767                 ex.dom = elm;
12768             } else {
12769                 ex = El.addToCache(new El(elm));
12770             }
12771             return ex;
12772         } else if (el.tagName) {
12773             // dom element
12774             if (! (id = el.id)) {
12775                 id = Ext.id(el);
12776             }
12777             if (EC[id] && EC[id].el) {
12778                 ex = EC[id].el;
12779                 ex.dom = el;
12780             } else {
12781                 ex = El.addToCache(new El(el));
12782             }
12783             return ex;
12784         } else if (el instanceof El) {
12785             if (el != docEl) {
12786                 // refresh dom element in case no longer valid,
12787                 // catch case where it hasn't been appended
12788                 // If an el instance is passed, don't pass to getElementById without some kind of id
12789                 if (Ext.isIE && (el.id == undefined || el.id == '')) {
12790                     el.dom = el.dom;
12791                 } else {
12792                     el.dom = DOC.getElementById(el.id) || el.dom;
12793                 }
12794             }
12795             return el;
12796         } else if (el.isComposite) {
12797             return el;
12798         } else if (Ext.isArray(el)) {
12799             return El.select(el);
12800         } else if (el == DOC) {
12801             // create a bogus element object representing the document object
12802             if (!docEl) {
12803                 var f = function() {};
12804                 f.prototype = El.prototype;
12805                 docEl = new f();
12806                 docEl.dom = DOC;
12807             }
12808             return docEl;
12809         }
12810         return null;
12811     };
12812
12813     El.addToCache = function(el, id) {
12814         if (el) {
12815             id = id || el.id;
12816             EC[id] = {
12817                 el: el,
12818                 data: {},
12819                 events: {}
12820             };
12821         }
12822         return el;
12823     };
12824
12825     // private method for getting and setting element data
12826     El.data = function(el, key, value) {
12827         el = El.get(el);
12828         if (!el) {
12829             return null;
12830         }
12831         var c = EC[el.id].data;
12832         if (arguments.length == 2) {
12833             return c[key];
12834         } else {
12835             return (c[key] = value);
12836         }
12837     };
12838
12839     // private
12840     // Garbage collection - uncache elements/purge listeners on orphaned elements
12841     // so we don't hold a reference and cause the browser to retain them
12842     function garbageCollect() {
12843         if (!Ext.enableGarbageCollector) {
12844             clearInterval(El.collectorThreadId);
12845         } else {
12846             var eid,
12847             el,
12848             d,
12849             o;
12850
12851             for (eid in EC) {
12852                 if (!EC.hasOwnProperty(eid)) {
12853                     continue;
12854                 }
12855                 o = EC[eid];
12856                 if (o.skipGarbageCollection) {
12857                     continue;
12858                 }
12859                 el = o.el;
12860                 d = el.dom;
12861                 // -------------------------------------------------------
12862                 // Determining what is garbage:
12863                 // -------------------------------------------------------
12864                 // !d
12865                 // dom node is null, definitely garbage
12866                 // -------------------------------------------------------
12867                 // !d.parentNode
12868                 // no parentNode == direct orphan, definitely garbage
12869                 // -------------------------------------------------------
12870                 // !d.offsetParent && !document.getElementById(eid)
12871                 // display none elements have no offsetParent so we will
12872                 // also try to look it up by it's id. However, check
12873                 // offsetParent first so we don't do unneeded lookups.
12874                 // This enables collection of elements that are not orphans
12875                 // directly, but somewhere up the line they have an orphan
12876                 // parent.
12877                 // -------------------------------------------------------
12878                 if (!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))) {
12879                     if (d && Ext.enableListenerCollection) {
12880                         Ext.EventManager.removeAll(d);
12881                     }
12882                     delete EC[eid];
12883                 }
12884             }
12885             // Cleanup IE Object leaks
12886             if (Ext.isIE) {
12887                 var t = {};
12888                 for (eid in EC) {
12889                     if (!EC.hasOwnProperty(eid)) {
12890                         continue;
12891                     }
12892                     t[eid] = EC[eid];
12893                 }
12894                 EC = Ext.cache = t;
12895             }
12896         }
12897     }
12898     El.collectorThreadId = setInterval(garbageCollect, 30000);
12899
12900     var flyFn = function() {};
12901     flyFn.prototype = El.prototype;
12902
12903     // dom is optional
12904     El.Flyweight = function(dom) {
12905         this.dom = dom;
12906     };
12907
12908     El.Flyweight.prototype = new flyFn();
12909     El.Flyweight.prototype.isFlyweight = true;
12910     El._flyweights = {};
12911
12912     /**
12913      * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
12914      * the dom node can be overwritten by other code. Shorthand of {@link Ext.core.Element#fly}</p>
12915      * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
12916      * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get Ext.get}
12917      * will be more appropriate to take advantage of the caching provided by the Ext.core.Element class.</p>
12918      * @param {String/HTMLElement} el The dom node or id
12919      * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
12920      * (e.g. internally Ext uses "_global")
12921      * @return {Element} The shared Element object (or null if no matching element was found)
12922      * @member Ext.core.Element
12923      * @method fly
12924      */
12925     El.fly = function(el, named) {
12926         var ret = null;
12927         named = named || '_global';
12928         el = Ext.getDom(el);
12929         if (el) {
12930             (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
12931             ret = El._flyweights[named];
12932         }
12933         return ret;
12934     };
12935
12936     /**
12937      * Retrieves Ext.core.Element objects.
12938      * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
12939      * retrieves Ext.core.Element objects which encapsulate DOM elements. To retrieve a Component by
12940      * its ID, use {@link Ext.ComponentManager#get}.</p>
12941      * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
12942      * object was recreated with the same id via AJAX or DOM.</p>
12943      * Shorthand of {@link Ext.core.Element#get}
12944      * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
12945      * @return {Element} The Element object (or null if no matching element was found)
12946      * @member Ext
12947      * @method get
12948      */
12949     Ext.get = El.get;
12950
12951     /**
12952      * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
12953      * the dom node can be overwritten by other code. Shorthand of {@link Ext.core.Element#fly}</p>
12954      * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
12955      * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get Ext.get}
12956      * will be more appropriate to take advantage of the caching provided by the Ext.core.Element class.</p>
12957      * @param {String/HTMLElement} el The dom node or id
12958      * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
12959      * (e.g. internally Ext uses "_global")
12960      * @return {Element} The shared Element object (or null if no matching element was found)
12961      * @member Ext
12962      * @method fly
12963      */
12964     Ext.fly = El.fly;
12965
12966     // speedy lookup for elements never to box adjust
12967     var noBoxAdjust = Ext.isStrict ? {
12968         select: 1
12969     }: {
12970         input: 1,
12971         select: 1,
12972         textarea: 1
12973     };
12974     if (Ext.isIE || Ext.isGecko) {
12975         noBoxAdjust['button'] = 1;
12976     }
12977 })();
12978
12979 /**
12980  * @class Ext.core.Element
12981  */
12982 Ext.core.Element.addMethods({
12983     /**
12984      * 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)
12985      * @param {String} selector The simple selector to test
12986      * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 50 || document.body)
12987      * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
12988      * @return {HTMLElement} The matching DOM node (or null if no match was found)
12989      */
12990     findParent : function(simpleSelector, maxDepth, returnEl) {
12991         var p = this.dom,
12992             b = document.body,
12993             depth = 0,
12994             stopEl;
12995
12996         maxDepth = maxDepth || 50;
12997         if (isNaN(maxDepth)) {
12998             stopEl = Ext.getDom(maxDepth);
12999             maxDepth = Number.MAX_VALUE;
13000         }
13001         while (p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl) {
13002             if (Ext.DomQuery.is(p, simpleSelector)) {
13003                 return returnEl ? Ext.get(p) : p;
13004             }
13005             depth++;
13006             p = p.parentNode;
13007         }
13008         return null;
13009     },
13010     
13011     /**
13012      * Looks at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
13013      * @param {String} selector The simple selector to test
13014      * @param {Number/Mixed} maxDepth (optional) The max depth to
13015             search as a number or element (defaults to 10 || document.body)
13016      * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
13017      * @return {HTMLElement} The matching DOM node (or null if no match was found)
13018      */
13019     findParentNode : function(simpleSelector, maxDepth, returnEl) {
13020         var p = Ext.fly(this.dom.parentNode, '_internal');
13021         return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
13022     },
13023
13024     /**
13025      * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. div.some-class or span:first-child).
13026      * This is a shortcut for findParentNode() that always returns an Ext.core.Element.
13027      * @param {String} selector The simple selector to test
13028      * @param {Number/Mixed} maxDepth (optional) The max depth to
13029             search as a number or element (defaults to 10 || document.body)
13030      * @return {Ext.core.Element} The matching DOM node (or null if no match was found)
13031      */
13032     up : function(simpleSelector, maxDepth) {
13033         return this.findParentNode(simpleSelector, maxDepth, true);
13034     },
13035
13036     /**
13037      * Creates a {@link Ext.CompositeElement} for child nodes based on the passed CSS selector (the selector should not contain an id).
13038      * @param {String} selector The CSS selector
13039      * @return {CompositeElement/CompositeElement} The composite element
13040      */
13041     select : function(selector) {
13042         return Ext.core.Element.select(selector, false,  this.dom);
13043     },
13044
13045     /**
13046      * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
13047      * @param {String} selector The CSS selector
13048      * @return {Array} An array of the matched nodes
13049      */
13050     query : function(selector) {
13051         return Ext.DomQuery.select(selector, this.dom);
13052     },
13053
13054     /**
13055      * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
13056      * @param {String} selector The CSS selector
13057      * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.core.Element (defaults to false)
13058      * @return {HTMLElement/Ext.core.Element} The child Ext.core.Element (or DOM node if returnDom = true)
13059      */
13060     down : function(selector, returnDom) {
13061         var n = Ext.DomQuery.selectNode(selector, this.dom);
13062         return returnDom ? n : Ext.get(n);
13063     },
13064
13065     /**
13066      * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
13067      * @param {String} selector The CSS selector
13068      * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.core.Element (defaults to false)
13069      * @return {HTMLElement/Ext.core.Element} The child Ext.core.Element (or DOM node if returnDom = true)
13070      */
13071     child : function(selector, returnDom) {
13072         var node,
13073             me = this,
13074             id;
13075         id = Ext.get(me).id;
13076         // Escape . or :
13077         id = id.replace(/[\.:]/g, "\\$0");
13078         node = Ext.DomQuery.selectNode('#' + id + " > " + selector, me.dom);
13079         return returnDom ? node : Ext.get(node);
13080     },
13081
13082      /**
13083      * Gets the parent node for this element, optionally chaining up trying to match a selector
13084      * @param {String} selector (optional) Find a parent node that matches the passed simple selector
13085      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
13086      * @return {Ext.core.Element/HTMLElement} The parent node or null
13087      */
13088     parent : function(selector, returnDom) {
13089         return this.matchNode('parentNode', 'parentNode', selector, returnDom);
13090     },
13091
13092      /**
13093      * Gets the next sibling, skipping text nodes
13094      * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
13095      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
13096      * @return {Ext.core.Element/HTMLElement} The next sibling or null
13097      */
13098     next : function(selector, returnDom) {
13099         return this.matchNode('nextSibling', 'nextSibling', selector, returnDom);
13100     },
13101
13102     /**
13103      * Gets the previous sibling, skipping text nodes
13104      * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
13105      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
13106      * @return {Ext.core.Element/HTMLElement} The previous sibling or null
13107      */
13108     prev : function(selector, returnDom) {
13109         return this.matchNode('previousSibling', 'previousSibling', selector, returnDom);
13110     },
13111
13112
13113     /**
13114      * Gets the first child, skipping text nodes
13115      * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
13116      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
13117      * @return {Ext.core.Element/HTMLElement} The first child or null
13118      */
13119     first : function(selector, returnDom) {
13120         return this.matchNode('nextSibling', 'firstChild', selector, returnDom);
13121     },
13122
13123     /**
13124      * Gets the last child, skipping text nodes
13125      * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
13126      * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
13127      * @return {Ext.core.Element/HTMLElement} The last child or null
13128      */
13129     last : function(selector, returnDom) {
13130         return this.matchNode('previousSibling', 'lastChild', selector, returnDom);
13131     },
13132
13133     matchNode : function(dir, start, selector, returnDom) {
13134         if (!this.dom) {
13135             return null;
13136         }
13137         
13138         var n = this.dom[start];
13139         while (n) {
13140             if (n.nodeType == 1 && (!selector || Ext.DomQuery.is(n, selector))) {
13141                 return !returnDom ? Ext.get(n) : n;
13142             }
13143             n = n[dir];
13144         }
13145         return null;
13146     }
13147 });
13148
13149 /**
13150  * @class Ext.core.Element
13151  */
13152 Ext.core.Element.addMethods({
13153     /**
13154      * Appends the passed element(s) to this element
13155      * @param {String/HTMLElement/Array/Element/CompositeElement} el
13156      * @return {Ext.core.Element} this
13157      */
13158     appendChild : function(el) {
13159         return Ext.get(el).appendTo(this);
13160     },
13161
13162     /**
13163      * Appends this element to the passed element
13164      * @param {Mixed} el The new parent element
13165      * @return {Ext.core.Element} this
13166      */
13167     appendTo : function(el) {
13168         Ext.getDom(el).appendChild(this.dom);
13169         return this;
13170     },
13171
13172     /**
13173      * Inserts this element before the passed element in the DOM
13174      * @param {Mixed} el The element before which this element will be inserted
13175      * @return {Ext.core.Element} this
13176      */
13177     insertBefore : function(el) {
13178         el = Ext.getDom(el);
13179         el.parentNode.insertBefore(this.dom, el);
13180         return this;
13181     },
13182
13183     /**
13184      * Inserts this element after the passed element in the DOM
13185      * @param {Mixed} el The element to insert after
13186      * @return {Ext.core.Element} this
13187      */
13188     insertAfter : function(el) {
13189         el = Ext.getDom(el);
13190         el.parentNode.insertBefore(this.dom, el.nextSibling);
13191         return this;
13192     },
13193
13194     /**
13195      * Inserts (or creates) an element (or DomHelper config) as the first child of this element
13196      * @param {Mixed/Object} el The id or element to insert or a DomHelper config to create and insert
13197      * @return {Ext.core.Element} The new child
13198      */
13199     insertFirst : function(el, returnDom) {
13200         el = el || {};
13201         if (el.nodeType || el.dom || typeof el == 'string') { // element
13202             el = Ext.getDom(el);
13203             this.dom.insertBefore(el, this.dom.firstChild);
13204             return !returnDom ? Ext.get(el) : el;
13205         }
13206         else { // dh config
13207             return this.createChild(el, this.dom.firstChild, returnDom);
13208         }
13209     },
13210
13211     /**
13212      * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
13213      * @param {Mixed/Object/Array} el The id, element to insert or a DomHelper config to create and insert *or* an array of any of those.
13214      * @param {String} where (optional) 'before' or 'after' defaults to before
13215      * @param {Boolean} returnDom (optional) True to return the .;ll;l,raw DOM element instead of Ext.core.Element
13216      * @return {Ext.core.Element} The inserted Element. If an array is passed, the last inserted element is returned.
13217      */
13218     insertSibling: function(el, where, returnDom){
13219         var me = this, rt,
13220         isAfter = (where || 'before').toLowerCase() == 'after',
13221         insertEl;
13222
13223         if(Ext.isArray(el)){
13224             insertEl = me;
13225             Ext.each(el, function(e) {
13226                 rt = Ext.fly(insertEl, '_internal').insertSibling(e, where, returnDom);
13227                 if(isAfter){
13228                     insertEl = rt;
13229                 }
13230             });
13231             return rt;
13232         }
13233
13234         el = el || {};
13235
13236         if(el.nodeType || el.dom){
13237             rt = me.dom.parentNode.insertBefore(Ext.getDom(el), isAfter ? me.dom.nextSibling : me.dom);
13238             if (!returnDom) {
13239                 rt = Ext.get(rt);
13240             }
13241         }else{
13242             if (isAfter && !me.dom.nextSibling) {
13243                 rt = Ext.core.DomHelper.append(me.dom.parentNode, el, !returnDom);
13244             } else {
13245                 rt = Ext.core.DomHelper[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
13246             }
13247         }
13248         return rt;
13249     },
13250
13251     /**
13252      * Replaces the passed element with this element
13253      * @param {Mixed} el The element to replace
13254      * @return {Ext.core.Element} this
13255      */
13256     replace : function(el) {
13257         el = Ext.get(el);
13258         this.insertBefore(el);
13259         el.remove();
13260         return this;
13261     },
13262     
13263     /**
13264      * Replaces this element with the passed element
13265      * @param {Mixed/Object} el The new element or a DomHelper config of an element to create
13266      * @return {Ext.core.Element} this
13267      */
13268     replaceWith: function(el){
13269         var me = this;
13270             
13271         if(el.nodeType || el.dom || typeof el == 'string'){
13272             el = Ext.get(el);
13273             me.dom.parentNode.insertBefore(el, me.dom);
13274         }else{
13275             el = Ext.core.DomHelper.insertBefore(me.dom, el);
13276         }
13277         
13278         delete Ext.cache[me.id];
13279         Ext.removeNode(me.dom);      
13280         me.id = Ext.id(me.dom = el);
13281         Ext.core.Element.addToCache(me.isFlyweight ? new Ext.core.Element(me.dom) : me);     
13282         return me;
13283     },
13284     
13285     /**
13286      * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
13287      * @param {Object} config DomHelper element config object.  If no tag is specified (e.g., {tag:'input'}) then a div will be
13288      * automatically generated with the specified attributes.
13289      * @param {HTMLElement} insertBefore (optional) a child element of this element
13290      * @param {Boolean} returnDom (optional) true to return the dom node instead of creating an Element
13291      * @return {Ext.core.Element} The new child element
13292      */
13293     createChild : function(config, insertBefore, returnDom) {
13294         config = config || {tag:'div'};
13295         if (insertBefore) {
13296             return Ext.core.DomHelper.insertBefore(insertBefore, config, returnDom !== true);
13297         }
13298         else {
13299             return Ext.core.DomHelper[!this.dom.firstChild ? 'insertFirst' : 'append'](this.dom, config,  returnDom !== true);
13300         }
13301     },
13302
13303     /**
13304      * Creates and wraps this element with another element
13305      * @param {Object} config (optional) DomHelper element config object for the wrapper element or null for an empty div
13306      * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.core.Element
13307      * @return {HTMLElement/Element} The newly created wrapper element
13308      */
13309     wrap : function(config, returnDom) {
13310         var newEl = Ext.core.DomHelper.insertBefore(this.dom, config || {tag: "div"}, !returnDom),
13311             d = newEl.dom || newEl;
13312
13313         d.appendChild(this.dom);
13314         return newEl;
13315     },
13316
13317     /**
13318      * Inserts an html fragment into this element
13319      * @param {String} where Where to insert the html in relation to this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
13320      * @param {String} html The HTML fragment
13321      * @param {Boolean} returnEl (optional) True to return an Ext.core.Element (defaults to false)
13322      * @return {HTMLElement/Ext.core.Element} The inserted node (or nearest related if more than 1 inserted)
13323      */
13324     insertHtml : function(where, html, returnEl) {
13325         var el = Ext.core.DomHelper.insertHtml(where, this.dom, html);
13326         return returnEl ? Ext.get(el) : el;
13327     }
13328 });
13329
13330 /**
13331  * @class Ext.core.Element
13332  */
13333 (function(){
13334     Ext.core.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>';
13335     // local style camelizing for speed
13336     var supports = Ext.supports,
13337         view = document.defaultView,
13338         opacityRe = /alpha\(opacity=(.*)\)/i,
13339         trimRe = /^\s+|\s+$/g,
13340         spacesRe = /\s+/,
13341         wordsRe = /\w/g,
13342         adjustDirect2DTableRe = /table-row|table-.*-group/,
13343         INTERNAL = '_internal',
13344         PADDING = 'padding',
13345         MARGIN = 'margin',
13346         BORDER = 'border',
13347         LEFT = '-left',
13348         RIGHT = '-right',
13349         TOP = '-top',
13350         BOTTOM = '-bottom',
13351         WIDTH = '-width',
13352         MATH = Math,
13353         HIDDEN = 'hidden',
13354         ISCLIPPED = 'isClipped',
13355         OVERFLOW = 'overflow',
13356         OVERFLOWX = 'overflow-x',
13357         OVERFLOWY = 'overflow-y',
13358         ORIGINALCLIP = 'originalClip',
13359         // special markup used throughout Ext when box wrapping elements
13360         borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
13361         paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
13362         margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
13363         data = Ext.core.Element.data;
13364
13365     Ext.override(Ext.core.Element, {
13366         
13367         /**
13368          * TODO: Look at this
13369          */
13370         // private  ==> used by Fx
13371         adjustWidth : function(width) {
13372             var me = this,
13373                 isNum = (typeof width == 'number');
13374                 
13375             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
13376                width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
13377             }
13378             return (isNum && width < 0) ? 0 : width;
13379         },
13380
13381         // private   ==> used by Fx
13382         adjustHeight : function(height) {
13383             var me = this,
13384                 isNum = (typeof height == "number");
13385                 
13386             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
13387                height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
13388             }
13389             return (isNum && height < 0) ? 0 : height;
13390         },
13391
13392
13393         /**
13394          * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
13395          * @param {String/Array} className The CSS classes to add separated by space, or an array of classes
13396          * @return {Ext.core.Element} this
13397          */
13398         addCls : function(className){
13399             var me = this,
13400                 cls = [],
13401                 space = ((me.dom.className.replace(trimRe, '') == '') ? "" : " "),
13402                 i, len, v;
13403             if (!Ext.isDefined(className)) {
13404                 return me;
13405             }
13406             // Separate case is for speed
13407             if (!Ext.isArray(className)) {
13408                 if (typeof className === 'string') {
13409                     className = className.replace(trimRe, '').split(spacesRe);
13410                     if (className.length === 1) {
13411                         className = className[0];
13412                         if (!me.hasCls(className)) {
13413                             me.dom.className += space + className;
13414                         }
13415                     } else {
13416                         this.addCls(className);
13417                     }
13418                 }
13419             } else {
13420                 for (i = 0, len = className.length; i < len; i++) {
13421                     v = className[i];
13422                     if (typeof v == 'string' && (' ' + me.dom.className + ' ').indexOf(' ' + v + ' ') == -1) {
13423                         cls.push(v);
13424                     }
13425                 }
13426                 if (cls.length) {
13427                     me.dom.className += space + cls.join(" ");
13428                 }
13429             }
13430             return me;
13431         },
13432
13433         /**
13434          * Removes one or more CSS classes from the element.
13435          * @param {String/Array} className The CSS classes to remove separated by space, or an array of classes
13436          * @return {Ext.core.Element} this
13437          */
13438         removeCls : function(className){
13439             var me = this,
13440                 i, idx, len, cls, elClasses;
13441             if (!Ext.isDefined(className)) {
13442                 return me;
13443             }
13444             if (!Ext.isArray(className)){
13445                 className = className.replace(trimRe, '').split(spacesRe);
13446             }
13447             if (me.dom && me.dom.className) {
13448                 elClasses = me.dom.className.replace(trimRe, '').split(spacesRe);
13449                 for (i = 0, len = className.length; i < len; i++) {
13450                     cls = className[i];
13451                     if (typeof cls == 'string') {
13452                         cls = cls.replace(trimRe, '');
13453                         idx = Ext.Array.indexOf(elClasses, cls);
13454                         if (idx != -1) {
13455                             elClasses.splice(idx, 1);
13456                         }
13457                     }
13458                 }
13459                 me.dom.className = elClasses.join(" ");
13460             }
13461             return me;
13462         },
13463
13464         /**
13465          * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
13466          * @param {String/Array} className The CSS class to add, or an array of classes
13467          * @return {Ext.core.Element} this
13468          */
13469         radioCls : function(className){
13470             var cn = this.dom.parentNode.childNodes,
13471                 v, i, len;
13472             className = Ext.isArray(className) ? className : [className];
13473             for (i = 0, len = cn.length; i < len; i++) {
13474                 v = cn[i];
13475                 if (v && v.nodeType == 1) {
13476                     Ext.fly(v, '_internal').removeCls(className);
13477                 }
13478             }
13479             return this.addCls(className);
13480         },
13481
13482         /**
13483          * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
13484          * @param {String} className The CSS class to toggle
13485          * @return {Ext.core.Element} this
13486          * @method
13487          */
13488         toggleCls : Ext.supports.ClassList ?
13489             function(className) {
13490                 this.dom.classList.toggle(Ext.String.trim(className));
13491                 return this;
13492             } :
13493             function(className) {
13494                 return this.hasCls(className) ? this.removeCls(className) : this.addCls(className);
13495             },
13496
13497         /**
13498          * Checks if the specified CSS class exists on this element's DOM node.
13499          * @param {String} className The CSS class to check for
13500          * @return {Boolean} True if the class exists, else false
13501          * @method
13502          */
13503         hasCls : Ext.supports.ClassList ?
13504             function(className) {
13505                 if (!className) {
13506                     return false;
13507                 }
13508                 className = className.split(spacesRe);
13509                 var ln = className.length,
13510                     i = 0;
13511                 for (; i < ln; i++) {
13512                     if (className[i] && this.dom.classList.contains(className[i])) {
13513                         return true;
13514                     }
13515                 }
13516                 return false;
13517             } :
13518             function(className){
13519                 return className && (' ' + this.dom.className + ' ').indexOf(' ' + className + ' ') != -1;
13520             },
13521
13522         /**
13523          * Replaces a CSS class on the element with another.  If the old name does not exist, the new name will simply be added.
13524          * @param {String} oldClassName The CSS class to replace
13525          * @param {String} newClassName The replacement CSS class
13526          * @return {Ext.core.Element} this
13527          */
13528         replaceCls : function(oldClassName, newClassName){
13529             return this.removeCls(oldClassName).addCls(newClassName);
13530         },
13531
13532         isStyle : function(style, val) {
13533             return this.getStyle(style) == val;
13534         },
13535
13536         /**
13537          * Normalizes currentStyle and computedStyle.
13538          * @param {String} property The style property whose value is returned.
13539          * @return {String} The current value of the style property for this element.
13540          * @method
13541          */
13542         getStyle : function(){
13543             return view && view.getComputedStyle ?
13544                 function(prop){
13545                     var el = this.dom,
13546                         v, cs, out, display, cleaner;
13547
13548                     if(el == document){
13549                         return null;
13550                     }
13551                     prop = Ext.core.Element.normalize(prop);
13552                     out = (v = el.style[prop]) ? v :
13553                            (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
13554                            
13555                     // Ignore cases when the margin is correctly reported as 0, the bug only shows
13556                     // numbers larger.
13557                     if(prop == 'marginRight' && out != '0px' && !supports.RightMargin){
13558                         cleaner = Ext.core.Element.getRightMarginFixCleaner(el);
13559                         display = this.getStyle('display');
13560                         el.style.display = 'inline-block';
13561                         out = view.getComputedStyle(el, '').marginRight;
13562                         el.style.display = display;
13563                         cleaner();
13564                     }
13565                     
13566                     if(prop == 'backgroundColor' && out == 'rgba(0, 0, 0, 0)' && !supports.TransparentColor){
13567                         out = 'transparent';
13568                     }
13569                     return out;
13570                 } :
13571                 function(prop){
13572                     var el = this.dom,
13573                         m, cs;
13574
13575                     if (el == document) {
13576                         return null;
13577                     }
13578                     
13579                     if (prop == 'opacity') {
13580                         if (el.style.filter.match) {
13581                             m = el.style.filter.match(opacityRe);
13582                             if(m){
13583                                 var fv = parseFloat(m[1]);
13584                                 if(!isNaN(fv)){
13585                                     return fv ? fv / 100 : 0;
13586                                 }
13587                             }
13588                         }
13589                         return 1;
13590                     }
13591                     prop = Ext.core.Element.normalize(prop);
13592                     return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
13593                 };
13594         }(),
13595
13596         /**
13597          * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
13598          * are convert to standard 6 digit hex color.
13599          * @param {String} attr The css attribute
13600          * @param {String} defaultValue The default value to use when a valid color isn't found
13601          * @param {String} prefix (optional) defaults to #. Use an empty string when working with
13602          * color anims.
13603          */
13604         getColor : function(attr, defaultValue, prefix){
13605             var v = this.getStyle(attr),
13606                 color = prefix || prefix === '' ? prefix : '#',
13607                 h;
13608
13609             if(!v || (/transparent|inherit/.test(v))) {
13610                 return defaultValue;
13611             }
13612             if(/^r/.test(v)){
13613                 Ext.each(v.slice(4, v.length -1).split(','), function(s){
13614                     h = parseInt(s, 10);
13615                     color += (h < 16 ? '0' : '') + h.toString(16);
13616                 });
13617             }else{
13618                 v = v.replace('#', '');
13619                 color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
13620             }
13621             return(color.length > 5 ? color.toLowerCase() : defaultValue);
13622         },
13623
13624         /**
13625          * Wrapper for setting style properties, also takes single object parameter of multiple styles.
13626          * @param {String/Object} property The style property to be set, or an object of multiple styles.
13627          * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
13628          * @return {Ext.core.Element} this
13629          */
13630         setStyle : function(prop, value){
13631             var me = this,
13632                 tmp, style;
13633
13634             if (!me.dom) {
13635                 return me;
13636             }
13637
13638             if (!Ext.isObject(prop)) {
13639                 tmp = {};
13640                 tmp[prop] = value;
13641                 prop = tmp;
13642             }
13643             for (style in prop) {
13644                 if (prop.hasOwnProperty(style)) {
13645                     value = Ext.value(prop[style], '');
13646                     if (style == 'opacity') {
13647                         me.setOpacity(value);
13648                     }
13649                     else {
13650                         me.dom.style[Ext.core.Element.normalize(style)] = value;
13651                     }
13652                 }
13653             }
13654             return me;
13655         },
13656
13657         /**
13658          * Set the opacity of the element
13659          * @param {Float} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
13660          * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
13661          * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
13662          * @return {Ext.core.Element} this
13663          */
13664         setOpacity: function(opacity, animate) {
13665             var me = this,
13666                 dom = me.dom,
13667                 val,
13668                 style;
13669
13670             if (!me.dom) {
13671                 return me;
13672             }
13673
13674             style = me.dom.style;
13675
13676             if (!animate || !me.anim) {
13677                 if (!Ext.supports.Opacity) {
13678                     opacity = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')': '';
13679                     val = style.filter.replace(opacityRe, '').replace(trimRe, '');
13680
13681                     style.zoom = 1;
13682                     style.filter = val + (val.length > 0 ? ' ': '') + opacity;
13683                 }
13684                 else {
13685                     style.opacity = opacity;
13686                 }
13687             }
13688             else {
13689                 if (!Ext.isObject(animate)) {
13690                     animate = {
13691                         duration: 350,
13692                         easing: 'ease-in'
13693                     };
13694                 }
13695                 me.animate(Ext.applyIf({
13696                     to: {
13697                         opacity: opacity
13698                     }
13699                 },
13700                 animate));
13701             }
13702             return me;
13703         },
13704
13705
13706         /**
13707          * Clears any opacity settings from this element. Required in some cases for IE.
13708          * @return {Ext.core.Element} this
13709          */
13710         clearOpacity : function(){
13711             var style = this.dom.style;
13712             if(!Ext.supports.Opacity){
13713                 if(!Ext.isEmpty(style.filter)){
13714                     style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
13715                 }
13716             }else{
13717                 style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
13718             }
13719             return this;
13720         },
13721         
13722         /**
13723          * @private
13724          * Returns 1 if the browser returns the subpixel dimension rounded to the lowest pixel.
13725          * @return {Number} 0 or 1 
13726          */
13727         adjustDirect2DDimension: function(dimension) {
13728             var me = this,
13729                 dom = me.dom,
13730                 display = me.getStyle('display'),
13731                 inlineDisplay = dom.style['display'],
13732                 inlinePosition = dom.style['position'],
13733                 originIndex = dimension === 'width' ? 0 : 1,
13734                 floating;
13735                 
13736             if (display === 'inline') {
13737                 dom.style['display'] = 'inline-block';
13738             }
13739
13740             dom.style['position'] = display.match(adjustDirect2DTableRe) ? 'absolute' : 'static';
13741
13742             // floating will contain digits that appears after the decimal point
13743             // if height or width are set to auto we fallback to msTransformOrigin calculation
13744             floating = (parseFloat(me.getStyle(dimension)) || parseFloat(dom.currentStyle.msTransformOrigin.split(' ')[originIndex]) * 2) % 1;
13745             
13746             dom.style['position'] = inlinePosition;
13747             
13748             if (display === 'inline') {
13749                 dom.style['display'] = inlineDisplay;
13750             }
13751
13752             return floating;
13753         },
13754         
13755         /**
13756          * Returns the offset height of the element
13757          * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
13758          * @return {Number} The element's height
13759          */
13760         getHeight: function(contentHeight, preciseHeight) {
13761             var me = this,
13762                 dom = me.dom,
13763                 hidden = Ext.isIE && me.isStyle('display', 'none'),
13764                 height, overflow, style, floating;
13765
13766             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
13767             // We will put the overflow back to it's original value when we are done measuring.
13768             if (Ext.isIEQuirks) {
13769                 style = dom.style;
13770                 overflow = style.overflow;
13771                 me.setStyle({ overflow: 'hidden'});
13772             }
13773
13774             height = dom.offsetHeight;
13775
13776             height = MATH.max(height, hidden ? 0 : dom.clientHeight) || 0;
13777
13778             // IE9 Direct2D dimension rounding bug
13779             if (!hidden && Ext.supports.Direct2DBug) {
13780                 floating = me.adjustDirect2DDimension('height');
13781                 if (preciseHeight) {
13782                     height += floating;
13783                 }
13784                 else if (floating > 0 && floating < 0.5) {
13785                     height++;
13786                 }
13787             }
13788
13789             if (contentHeight) {
13790                 height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
13791             }
13792
13793             if (Ext.isIEQuirks) {
13794                 me.setStyle({ overflow: overflow});
13795             }
13796
13797             if (height < 0) {
13798                 height = 0;
13799             }
13800             return height;
13801         },
13802                 
13803         /**
13804          * Returns the offset width of the element
13805          * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
13806          * @return {Number} The element's width
13807          */
13808         getWidth: function(contentWidth, preciseWidth) {
13809             var me = this,
13810                 dom = me.dom,
13811                 hidden = Ext.isIE && me.isStyle('display', 'none'),
13812                 rect, width, overflow, style, floating, parentPosition;
13813
13814             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
13815             // We will put the overflow back to it's original value when we are done measuring.
13816             if (Ext.isIEQuirks) {
13817                 style = dom.style;
13818                 overflow = style.overflow;
13819                 me.setStyle({overflow: 'hidden'});
13820             }
13821             
13822             // Fix Opera 10.5x width calculation issues 
13823             if (Ext.isOpera10_5) {
13824                 if (dom.parentNode.currentStyle.position === 'relative') {
13825                     parentPosition = dom.parentNode.style.position;
13826                     dom.parentNode.style.position = 'static';
13827                     width = dom.offsetWidth;
13828                     dom.parentNode.style.position = parentPosition;
13829                 }
13830                 width = Math.max(width || 0, dom.offsetWidth);
13831             
13832             // Gecko will in some cases report an offsetWidth that is actually less than the width of the
13833             // text contents, because it measures fonts with sub-pixel precision but rounds the calculated
13834             // value down. Using getBoundingClientRect instead of offsetWidth allows us to get the precise
13835             // subpixel measurements so we can force them to always be rounded up. See
13836             // https://bugzilla.mozilla.org/show_bug.cgi?id=458617
13837             } else if (Ext.supports.BoundingClientRect) {
13838                 rect = dom.getBoundingClientRect();
13839                 width = rect.right - rect.left;
13840                 width = preciseWidth ? width : Math.ceil(width);
13841             } else {
13842                 width = dom.offsetWidth;
13843             }
13844
13845             width = MATH.max(width, hidden ? 0 : dom.clientWidth) || 0;
13846
13847             // IE9 Direct2D dimension rounding bug
13848             if (!hidden && Ext.supports.Direct2DBug) {
13849                 floating = me.adjustDirect2DDimension('width');
13850                 if (preciseWidth) {
13851                     width += floating;
13852                 }
13853                 else if (floating > 0 && floating < 0.5) {
13854                     width++;
13855                 }
13856             }
13857             
13858             if (contentWidth) {
13859                 width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
13860             }
13861             
13862             if (Ext.isIEQuirks) {
13863                 me.setStyle({ overflow: overflow});
13864             }
13865
13866             if (width < 0) {
13867                 width = 0;
13868             }
13869             return width;
13870         },
13871
13872         /**
13873          * Set the width of this Element.
13874          * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
13875          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
13876          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
13877          * </ul></div>
13878          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
13879          * @return {Ext.core.Element} this
13880          */
13881         setWidth : function(width, animate){
13882             var me = this;
13883             width = me.adjustWidth(width);
13884             if (!animate || !me.anim) {
13885                 me.dom.style.width = me.addUnits(width);
13886             }
13887             else {
13888                 if (!Ext.isObject(animate)) {
13889                     animate = {};
13890                 }
13891                 me.animate(Ext.applyIf({
13892                     to: {
13893                         width: width
13894                     }
13895                 }, animate));
13896             }
13897             return me;
13898         },
13899
13900         /**
13901          * Set the height of this Element.
13902          * <pre><code>
13903 // change the height to 200px and animate with default configuration
13904 Ext.fly('elementId').setHeight(200, true);
13905
13906 // change the height to 150px and animate with a custom configuration
13907 Ext.fly('elId').setHeight(150, {
13908     duration : .5, // animation will have a duration of .5 seconds
13909     // will change the content to "finished"
13910     callback: function(){ this.{@link #update}("finished"); }
13911 });
13912          * </code></pre>
13913          * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
13914          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)</li>
13915          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
13916          * </ul></div>
13917          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
13918          * @return {Ext.core.Element} this
13919          */
13920          setHeight : function(height, animate){
13921             var me = this;
13922             height = me.adjustHeight(height);
13923             if (!animate || !me.anim) {
13924                 me.dom.style.height = me.addUnits(height);
13925             }
13926             else {
13927                 if (!Ext.isObject(animate)) {
13928                     animate = {};
13929                 }
13930                 me.animate(Ext.applyIf({
13931                     to: {
13932                         height: height
13933                     }
13934                 }, animate));
13935             }
13936             return me;
13937         },
13938
13939         /**
13940          * Gets the width of the border(s) for the specified side(s)
13941          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
13942          * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
13943          * @return {Number} The width of the sides passed added together
13944          */
13945         getBorderWidth : function(side){
13946             return this.addStyles(side, borders);
13947         },
13948
13949         /**
13950          * Gets the width of the padding(s) for the specified side(s)
13951          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
13952          * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
13953          * @return {Number} The padding of the sides passed added together
13954          */
13955         getPadding : function(side){
13956             return this.addStyles(side, paddings);
13957         },
13958
13959         /**
13960          *  Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
13961          * @return {Ext.core.Element} this
13962          */
13963         clip : function(){
13964             var me = this,
13965                 dom = me.dom;
13966
13967             if(!data(dom, ISCLIPPED)){
13968                 data(dom, ISCLIPPED, true);
13969                 data(dom, ORIGINALCLIP, {
13970                     o: me.getStyle(OVERFLOW),
13971                     x: me.getStyle(OVERFLOWX),
13972                     y: me.getStyle(OVERFLOWY)
13973                 });
13974                 me.setStyle(OVERFLOW, HIDDEN);
13975                 me.setStyle(OVERFLOWX, HIDDEN);
13976                 me.setStyle(OVERFLOWY, HIDDEN);
13977             }
13978             return me;
13979         },
13980
13981         /**
13982          *  Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
13983          * @return {Ext.core.Element} this
13984          */
13985         unclip : function(){
13986             var me = this,
13987                 dom = me.dom,
13988                 clip;
13989
13990             if(data(dom, ISCLIPPED)){
13991                 data(dom, ISCLIPPED, false);
13992                 clip = data(dom, ORIGINALCLIP);
13993                 if(o.o){
13994                     me.setStyle(OVERFLOW, o.o);
13995                 }
13996                 if(o.x){
13997                     me.setStyle(OVERFLOWX, o.x);
13998                 }
13999                 if(o.y){
14000                     me.setStyle(OVERFLOWY, o.y);
14001                 }
14002             }
14003             return me;
14004         },
14005
14006         // private
14007         addStyles : function(sides, styles){
14008             var totalSize = 0,
14009                 sidesArr = sides.match(wordsRe),
14010                 i = 0,
14011                 len = sidesArr.length,
14012                 side, size;
14013             for (; i < len; i++) {
14014                 side = sidesArr[i];
14015                 size = side && parseInt(this.getStyle(styles[side]), 10);
14016                 if (size) {
14017                     totalSize += MATH.abs(size);
14018                 }
14019             }
14020             return totalSize;
14021         },
14022
14023         margins : margins,
14024         
14025         /**
14026          * More flexible version of {@link #setStyle} for setting style properties.
14027          * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
14028          * a function which returns such a specification.
14029          * @return {Ext.core.Element} this
14030          */
14031         applyStyles : function(style){
14032             Ext.core.DomHelper.applyStyles(this.dom, style);
14033             return this;
14034         },
14035
14036         /**
14037          * Returns an object with properties matching the styles requested.
14038          * For example, el.getStyles('color', 'font-size', 'width') might return
14039          * {'color': '#FFFFFF', 'font-size': '13px', 'width': '100px'}.
14040          * @param {String} style1 A style name
14041          * @param {String} style2 A style name
14042          * @param {String} etc.
14043          * @return {Object} The style object
14044          */
14045         getStyles : function(){
14046             var styles = {},
14047                 len = arguments.length,
14048                 i = 0, style;
14049                 
14050             for(; i < len; ++i) {
14051                 style = arguments[i];
14052                 styles[style] = this.getStyle(style);
14053             }
14054             return styles;
14055         },
14056
14057        /**
14058         * <p>Wraps the specified element with a special 9 element markup/CSS block that renders by default as
14059         * a gray container with a gradient background, rounded corners and a 4-way shadow.</p>
14060         * <p>This special markup is used throughout Ext when box wrapping elements ({@link Ext.button.Button},
14061         * {@link Ext.panel.Panel} when <tt>{@link Ext.panel.Panel#frame frame=true}</tt>, {@link Ext.window.Window}).  The markup
14062         * is of this form:</p>
14063         * <pre><code>
14064     Ext.core.Element.boxMarkup =
14065     &#39;&lt;div class="{0}-tl">&lt;div class="{0}-tr">&lt;div class="{0}-tc">&lt;/div>&lt;/div>&lt;/div>
14066      &lt;div class="{0}-ml">&lt;div class="{0}-mr">&lt;div class="{0}-mc">&lt;/div>&lt;/div>&lt;/div>
14067      &lt;div class="{0}-bl">&lt;div class="{0}-br">&lt;div class="{0}-bc">&lt;/div>&lt;/div>&lt;/div>&#39;;
14068         * </code></pre>
14069         * <p>Example usage:</p>
14070         * <pre><code>
14071     // Basic box wrap
14072     Ext.get("foo").boxWrap();
14073
14074     // You can also add a custom class and use CSS inheritance rules to customize the box look.
14075     // 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
14076     // for how to create a custom box wrap style.
14077     Ext.get("foo").boxWrap().addCls("x-box-blue");
14078         * </code></pre>
14079         * @param {String} class (optional) A base CSS class to apply to the containing wrapper element
14080         * (defaults to <tt>'x-box'</tt>). Note that there are a number of CSS rules that are dependent on
14081         * this name to make the overall effect work, so if you supply an alternate base class, make sure you
14082         * also supply all of the necessary rules.
14083         * @return {Ext.core.Element} The outermost wrapping element of the created box structure.
14084         */
14085         boxWrap : function(cls){
14086             cls = cls || Ext.baseCSSPrefix + 'box';
14087             var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "'>" + Ext.String.format(Ext.core.Element.boxMarkup, cls) + "</div>"));
14088             Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
14089             return el;
14090         },
14091
14092         /**
14093          * Set the size of this Element. If animation is true, both width and height will be animated concurrently.
14094          * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
14095          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
14096          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
14097          * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
14098          * </ul></div>
14099          * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
14100          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).</li>
14101          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
14102          * </ul></div>
14103          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
14104          * @return {Ext.core.Element} this
14105          */
14106         setSize : function(width, height, animate){
14107             var me = this;
14108             if (Ext.isObject(width)){ // in case of object from getSize()
14109                 height = width.height;
14110                 width = width.width;
14111             }
14112             width = me.adjustWidth(width);
14113             height = me.adjustHeight(height);
14114             if(!animate || !me.anim){
14115                 me.dom.style.width = me.addUnits(width);
14116                 me.dom.style.height = me.addUnits(height);
14117             }
14118             else {
14119                 if (!Ext.isObject(animate)) {
14120                     animate = {};
14121                 }
14122                 me.animate(Ext.applyIf({
14123                     to: {
14124                         width: width,
14125                         height: height
14126                     }
14127                 }, animate));
14128             }
14129             return me;
14130         },
14131
14132         /**
14133          * Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders
14134          * when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements
14135          * if a height has not been set using CSS.
14136          * @return {Number}
14137          */
14138         getComputedHeight : function(){
14139             var me = this,
14140                 h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
14141             if(!h){
14142                 h = parseFloat(me.getStyle('height')) || 0;
14143                 if(!me.isBorderBox()){
14144                     h += me.getFrameWidth('tb');
14145                 }
14146             }
14147             return h;
14148         },
14149
14150         /**
14151          * Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders
14152          * when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements
14153          * if a width has not been set using CSS.
14154          * @return {Number}
14155          */
14156         getComputedWidth : function(){
14157             var me = this,
14158                 w = Math.max(me.dom.offsetWidth, me.dom.clientWidth);
14159                 
14160             if(!w){
14161                 w = parseFloat(me.getStyle('width')) || 0;
14162                 if(!me.isBorderBox()){
14163                     w += me.getFrameWidth('lr');
14164                 }
14165             }
14166             return w;
14167         },
14168
14169         /**
14170          * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
14171          for more information about the sides.
14172          * @param {String} sides
14173          * @return {Number}
14174          */
14175         getFrameWidth : function(sides, onlyContentBox){
14176             return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
14177         },
14178
14179         /**
14180          * Sets up event handlers to add and remove a css class when the mouse is over this element
14181          * @param {String} className
14182          * @return {Ext.core.Element} this
14183          */
14184         addClsOnOver : function(className){
14185             var dom = this.dom;
14186             this.hover(
14187                 function(){
14188                     Ext.fly(dom, INTERNAL).addCls(className);
14189                 },
14190                 function(){
14191                     Ext.fly(dom, INTERNAL).removeCls(className);
14192                 }
14193             );
14194             return this;
14195         },
14196
14197         /**
14198          * Sets up event handlers to add and remove a css class when this element has the focus
14199          * @param {String} className
14200          * @return {Ext.core.Element} this
14201          */
14202         addClsOnFocus : function(className){
14203             var me = this,
14204                 dom = me.dom;
14205             me.on("focus", function(){
14206                 Ext.fly(dom, INTERNAL).addCls(className);
14207             });
14208             me.on("blur", function(){
14209                 Ext.fly(dom, INTERNAL).removeCls(className);
14210             });
14211             return me;
14212         },
14213
14214         /**
14215          * 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)
14216          * @param {String} className
14217          * @return {Ext.core.Element} this
14218          */
14219         addClsOnClick : function(className){
14220             var dom = this.dom;
14221             this.on("mousedown", function(){
14222                 Ext.fly(dom, INTERNAL).addCls(className);
14223                 var d = Ext.getDoc(),
14224                     fn = function(){
14225                         Ext.fly(dom, INTERNAL).removeCls(className);
14226                         d.removeListener("mouseup", fn);
14227                     };
14228                 d.on("mouseup", fn);
14229             });
14230             return this;
14231         },
14232
14233         /**
14234          * <p>Returns the dimensions of the element available to lay content out in.<p>
14235          * <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
14236          * example:<pre><code>
14237         var vpSize = Ext.getBody().getViewSize();
14238
14239         // all Windows created afterwards will have a default value of 90% height and 95% width
14240         Ext.Window.override({
14241             width: vpSize.width * 0.9,
14242             height: vpSize.height * 0.95
14243         });
14244         // To handle window resizing you would have to hook onto onWindowResize.
14245         * </code></pre>
14246         *
14247         * getViewSize utilizes clientHeight/clientWidth which excludes sizing of scrollbars.
14248         * To obtain the size including scrollbars, use getStyleSize
14249         *
14250         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
14251         */
14252
14253         getViewSize : function(){
14254             var me = this,
14255                 dom = me.dom,
14256                 isDoc = (dom == Ext.getDoc().dom || dom == Ext.getBody().dom),
14257                 style, overflow, ret;
14258
14259             // If the body, use static methods
14260             if (isDoc) {
14261                 ret = {
14262                     width : Ext.core.Element.getViewWidth(),
14263                     height : Ext.core.Element.getViewHeight()
14264                 };
14265
14266             // Else use clientHeight/clientWidth
14267             }
14268             else {
14269                 // IE 6 & IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
14270                 // We will put the overflow back to it's original value when we are done measuring.
14271                 if (Ext.isIE6 || Ext.isIEQuirks) {
14272                     style = dom.style;
14273                     overflow = style.overflow;
14274                     me.setStyle({ overflow: 'hidden'});
14275                 }
14276                 ret = {
14277                     width : dom.clientWidth,
14278                     height : dom.clientHeight
14279                 };
14280                 if (Ext.isIE6 || Ext.isIEQuirks) {
14281                     me.setStyle({ overflow: overflow });
14282                 }
14283             }
14284             return ret;
14285         },
14286
14287         /**
14288         * <p>Returns the dimensions of the element available to lay content out in.<p>
14289         *
14290         * getStyleSize utilizes prefers style sizing if present, otherwise it chooses the larger of offsetHeight/clientHeight and offsetWidth/clientWidth.
14291         * To obtain the size excluding scrollbars, use getViewSize
14292         *
14293         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
14294         */
14295
14296         getStyleSize : function(){
14297             var me = this,
14298                 doc = document,
14299                 d = this.dom,
14300                 isDoc = (d == doc || d == doc.body),
14301                 s = d.style,
14302                 w, h;
14303
14304             // If the body, use static methods
14305             if (isDoc) {
14306                 return {
14307                     width : Ext.core.Element.getViewWidth(),
14308                     height : Ext.core.Element.getViewHeight()
14309                 };
14310             }
14311             // Use Styles if they are set
14312             if(s.width && s.width != 'auto'){
14313                 w = parseFloat(s.width);
14314                 if(me.isBorderBox()){
14315                    w -= me.getFrameWidth('lr');
14316                 }
14317             }
14318             // Use Styles if they are set
14319             if(s.height && s.height != 'auto'){
14320                 h = parseFloat(s.height);
14321                 if(me.isBorderBox()){
14322                    h -= me.getFrameWidth('tb');
14323                 }
14324             }
14325             // Use getWidth/getHeight if style not set.
14326             return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
14327         },
14328
14329         /**
14330          * Returns the size of the element.
14331          * @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
14332          * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
14333          */
14334         getSize : function(contentSize){
14335             return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
14336         },
14337
14338         /**
14339          * Forces the browser to repaint this element
14340          * @return {Ext.core.Element} this
14341          */
14342         repaint : function(){
14343             var dom = this.dom;
14344             this.addCls(Ext.baseCSSPrefix + 'repaint');
14345             setTimeout(function(){
14346                 Ext.fly(dom).removeCls(Ext.baseCSSPrefix + 'repaint');
14347             }, 1);
14348             return this;
14349         },
14350
14351         /**
14352          * Disables text selection for this element (normalized across browsers)
14353          * @return {Ext.core.Element} this
14354          */
14355         unselectable : function(){
14356             var me = this;
14357             me.dom.unselectable = "on";
14358
14359             me.swallowEvent("selectstart", true);
14360             me.applyStyles("-moz-user-select:none;-khtml-user-select:none;");
14361             me.addCls(Ext.baseCSSPrefix + 'unselectable');
14362             
14363             return me;
14364         },
14365
14366         /**
14367          * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
14368          * then it returns the calculated width of the sides (see getPadding)
14369          * @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
14370          * @return {Object/Number}
14371          */
14372         getMargin : function(side){
14373             var me = this,
14374                 hash = {t:"top", l:"left", r:"right", b: "bottom"},
14375                 o = {},
14376                 key;
14377
14378             if (!side) {
14379                 for (key in me.margins){
14380                     o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
14381                 }
14382                 return o;
14383             } else {
14384                 return me.addStyles.call(me, side, me.margins);
14385             }
14386         }
14387     });
14388 })();
14389 /**
14390  * @class Ext.core.Element
14391  */
14392 /**
14393  * Visibility mode constant for use with {@link #setVisibilityMode}. Use visibility to hide element
14394  * @static
14395  * @type Number
14396  */
14397 Ext.core.Element.VISIBILITY = 1;
14398 /**
14399  * Visibility mode constant for use with {@link #setVisibilityMode}. Use display to hide element
14400  * @static
14401  * @type Number
14402  */
14403 Ext.core.Element.DISPLAY = 2;
14404
14405 /**
14406  * Visibility mode constant for use with {@link #setVisibilityMode}. Use offsets (x and y positioning offscreen)
14407  * to hide element.
14408  * @static
14409  * @type Number
14410  */
14411 Ext.core.Element.OFFSETS = 3;
14412
14413
14414 Ext.core.Element.ASCLASS = 4;
14415
14416 /**
14417  * Defaults to 'x-hide-nosize'
14418  * @static
14419  * @type String
14420  */
14421 Ext.core.Element.visibilityCls = Ext.baseCSSPrefix + 'hide-nosize';
14422
14423 Ext.core.Element.addMethods(function(){
14424     var El = Ext.core.Element,
14425         OPACITY = "opacity",
14426         VISIBILITY = "visibility",
14427         DISPLAY = "display",
14428         HIDDEN = "hidden",
14429         OFFSETS = "offsets",
14430         ASCLASS = "asclass",
14431         NONE = "none",
14432         NOSIZE = 'nosize',
14433         ORIGINALDISPLAY = 'originalDisplay',
14434         VISMODE = 'visibilityMode',
14435         ISVISIBLE = 'isVisible',
14436         data = El.data,
14437         getDisplay = function(dom){
14438             var d = data(dom, ORIGINALDISPLAY);
14439             if(d === undefined){
14440                 data(dom, ORIGINALDISPLAY, d = '');
14441             }
14442             return d;
14443         },
14444         getVisMode = function(dom){
14445             var m = data(dom, VISMODE);
14446             if(m === undefined){
14447                 data(dom, VISMODE, m = 1);
14448             }
14449             return m;
14450         };
14451
14452     return {
14453         /**
14454          * The element's default display mode  (defaults to "")
14455          * @type String
14456          */
14457         originalDisplay : "",
14458         visibilityMode : 1,
14459
14460         /**
14461          * Sets the element's visibility mode. When setVisible() is called it
14462          * will use this to determine whether to set the visibility or the display property.
14463          * @param {Number} visMode Ext.core.Element.VISIBILITY or Ext.core.Element.DISPLAY
14464          * @return {Ext.core.Element} this
14465          */
14466         setVisibilityMode : function(visMode){
14467             data(this.dom, VISMODE, visMode);
14468             return this;
14469         },
14470
14471         /**
14472          * Checks whether the element is currently visible using both visibility and display properties.
14473          * @return {Boolean} True if the element is currently visible, else false
14474          */
14475         isVisible : function() {
14476             var me = this,
14477                 dom = me.dom,
14478                 visible = data(dom, ISVISIBLE);
14479
14480             if(typeof visible == 'boolean'){ //return the cached value if registered
14481                 return visible;
14482             }
14483             //Determine the current state based on display states
14484             visible = !me.isStyle(VISIBILITY, HIDDEN) &&
14485                       !me.isStyle(DISPLAY, NONE) &&
14486                       !((getVisMode(dom) == El.ASCLASS) && me.hasCls(me.visibilityCls || El.visibilityCls));
14487
14488             data(dom, ISVISIBLE, visible);
14489             return visible;
14490         },
14491
14492         /**
14493          * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
14494          * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
14495          * @param {Boolean} visible Whether the element is visible
14496          * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
14497          * @return {Ext.core.Element} this
14498          */
14499         setVisible : function(visible, animate){
14500             var me = this, isDisplay, isVisibility, isOffsets, isNosize,
14501                 dom = me.dom,
14502                 visMode = getVisMode(dom);
14503
14504
14505             // hideMode string override
14506             if (typeof animate == 'string'){
14507                 switch (animate) {
14508                     case DISPLAY:
14509                         visMode = El.DISPLAY;
14510                         break;
14511                     case VISIBILITY:
14512                         visMode = El.VISIBILITY;
14513                         break;
14514                     case OFFSETS:
14515                         visMode = El.OFFSETS;
14516                         break;
14517                     case NOSIZE:
14518                     case ASCLASS:
14519                         visMode = El.ASCLASS;
14520                         break;
14521                 }
14522                 me.setVisibilityMode(visMode);
14523                 animate = false;
14524             }
14525
14526             if (!animate || !me.anim) {
14527                 if(visMode == El.ASCLASS ){
14528
14529                     me[visible?'removeCls':'addCls'](me.visibilityCls || El.visibilityCls);
14530
14531                 } else if (visMode == El.DISPLAY){
14532
14533                     return me.setDisplayed(visible);
14534
14535                 } else if (visMode == El.OFFSETS){
14536
14537                     if (!visible){
14538                         // Remember position for restoring, if we are not already hidden by offsets.
14539                         if (!me.hideModeStyles) {
14540                             me.hideModeStyles = {
14541                                 position: me.getStyle('position'),
14542                                 top: me.getStyle('top'),
14543                                 left: me.getStyle('left')
14544                             };
14545                         }
14546                         me.applyStyles({position: 'absolute', top: '-10000px', left: '-10000px'});
14547                     }
14548
14549                     // Only "restore" as position if we have actually been hidden using offsets.
14550                     // Calling setVisible(true) on a positioned element should not reposition it.
14551                     else if (me.hideModeStyles) {
14552                         me.applyStyles(me.hideModeStyles || {position: '', top: '', left: ''});
14553                         delete me.hideModeStyles;
14554                     }
14555
14556                 }else{
14557                     me.fixDisplay();
14558                     // Show by clearing visibility style. Explicitly setting to "visible" overrides parent visibility setting.
14559                     dom.style.visibility = visible ? '' : HIDDEN;
14560                 }
14561             }else{
14562                 // closure for composites
14563                 if(visible){
14564                     me.setOpacity(0.01);
14565                     me.setVisible(true);
14566                 }
14567                 if (!Ext.isObject(animate)) {
14568                     animate = {
14569                         duration: 350,
14570                         easing: 'ease-in'
14571                     };
14572                 }
14573                 me.animate(Ext.applyIf({
14574                     callback: function() {
14575                         visible || me.setVisible(false).setOpacity(1);
14576                     },
14577                     to: {
14578                         opacity: (visible) ? 1 : 0
14579                     }
14580                 }, animate));
14581             }
14582             data(dom, ISVISIBLE, visible);  //set logical visibility state
14583             return me;
14584         },
14585
14586
14587         /**
14588          * @private
14589          * Determine if the Element has a relevant height and width available based
14590          * upon current logical visibility state
14591          */
14592         hasMetrics  : function(){
14593             var dom = this.dom;
14594             return this.isVisible() || (getVisMode(dom) == El.OFFSETS) || (getVisMode(dom) == El.VISIBILITY);
14595         },
14596
14597         /**
14598          * Toggles the element's visibility or display, depending on visibility mode.
14599          * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
14600          * @return {Ext.core.Element} this
14601          */
14602         toggle : function(animate){
14603             var me = this;
14604             me.setVisible(!me.isVisible(), me.anim(animate));
14605             return me;
14606         },
14607
14608         /**
14609          * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
14610          * @param {Mixed} value Boolean value to display the element using its default display, or a string to set the display directly.
14611          * @return {Ext.core.Element} this
14612          */
14613         setDisplayed : function(value) {
14614             if(typeof value == "boolean"){
14615                value = value ? getDisplay(this.dom) : NONE;
14616             }
14617             this.setStyle(DISPLAY, value);
14618             return this;
14619         },
14620
14621         // private
14622         fixDisplay : function(){
14623             var me = this;
14624             if (me.isStyle(DISPLAY, NONE)) {
14625                 me.setStyle(VISIBILITY, HIDDEN);
14626                 me.setStyle(DISPLAY, getDisplay(this.dom)); // first try reverting to default
14627                 if (me.isStyle(DISPLAY, NONE)) { // if that fails, default to block
14628                     me.setStyle(DISPLAY, "block");
14629                 }
14630             }
14631         },
14632
14633         /**
14634          * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
14635          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
14636          * @return {Ext.core.Element} this
14637          */
14638         hide : function(animate){
14639             // hideMode override
14640             if (typeof animate == 'string'){
14641                 this.setVisible(false, animate);
14642                 return this;
14643             }
14644             this.setVisible(false, this.anim(animate));
14645             return this;
14646         },
14647
14648         /**
14649         * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
14650         * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
14651          * @return {Ext.core.Element} this
14652          */
14653         show : function(animate){
14654             // hideMode override
14655             if (typeof animate == 'string'){
14656                 this.setVisible(true, animate);
14657                 return this;
14658             }
14659             this.setVisible(true, this.anim(animate));
14660             return this;
14661         }
14662     };
14663 }());
14664 /**
14665  * @class Ext.core.Element
14666  */
14667 Ext.applyIf(Ext.core.Element.prototype, {
14668     // @private override base Ext.util.Animate mixin for animate for backwards compatibility
14669     animate: function(config) {
14670         var me = this;
14671         if (!me.id) {
14672             me = Ext.get(me.dom);
14673         }
14674         if (Ext.fx.Manager.hasFxBlock(me.id)) {
14675             return me;
14676         }
14677         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(config)));
14678         return this;
14679     },
14680
14681     // @private override base Ext.util.Animate mixin for animate for backwards compatibility
14682     anim: function(config) {
14683         if (!Ext.isObject(config)) {
14684             return (config) ? {} : false;
14685         }
14686
14687         var me = this,
14688             duration = config.duration || Ext.fx.Anim.prototype.duration,
14689             easing = config.easing || 'ease',
14690             animConfig;
14691
14692         if (config.stopAnimation) {
14693             me.stopAnimation();
14694         }
14695
14696         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
14697
14698         // Clear any 'paused' defaults.
14699         Ext.fx.Manager.setFxDefaults(me.id, {
14700             delay: 0
14701         });
14702
14703         animConfig = {
14704             target: me,
14705             remove: config.remove,
14706             alternate: config.alternate || false,
14707             duration: duration,
14708             easing: easing,
14709             callback: config.callback,
14710             listeners: config.listeners,
14711             iterations: config.iterations || 1,
14712             scope: config.scope,
14713             block: config.block,
14714             concurrent: config.concurrent,
14715             delay: config.delay || 0,
14716             paused: true,
14717             keyframes: config.keyframes,
14718             from: config.from || {},
14719             to: Ext.apply({}, config)
14720         };
14721         Ext.apply(animConfig.to, config.to);
14722
14723         // Anim API properties - backward compat
14724         delete animConfig.to.to;
14725         delete animConfig.to.from;
14726         delete animConfig.to.remove;
14727         delete animConfig.to.alternate;
14728         delete animConfig.to.keyframes;
14729         delete animConfig.to.iterations;
14730         delete animConfig.to.listeners;
14731         delete animConfig.to.target;
14732         delete animConfig.to.paused;
14733         delete animConfig.to.callback;
14734         delete animConfig.to.scope;
14735         delete animConfig.to.duration;
14736         delete animConfig.to.easing;
14737         delete animConfig.to.concurrent;
14738         delete animConfig.to.block;
14739         delete animConfig.to.stopAnimation;
14740         delete animConfig.to.delay;
14741         return animConfig;
14742     },
14743
14744     /**
14745      * Slides the element into view.  An anchor point can be optionally passed to set the point of
14746      * origin for the slide effect.  This function automatically handles wrapping the element with
14747      * a fixed-size container if needed.  See the Fx class overview for valid anchor point options.
14748      * Usage:
14749      *<pre><code>
14750 // default: slide the element in from the top
14751 el.slideIn();
14752
14753 // custom: slide the element in from the right with a 2-second duration
14754 el.slideIn('r', { duration: 2 });
14755
14756 // common config options shown with default values
14757 el.slideIn('t', {
14758     easing: 'easeOut',
14759     duration: 500
14760 });
14761 </code></pre>
14762      * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
14763      * @param {Object} options (optional) Object literal with any of the Fx config options
14764      * @return {Ext.core.Element} The Element
14765      */
14766     slideIn: function(anchor, obj, slideOut) { 
14767         var me = this,
14768             elStyle = me.dom.style,
14769             beforeAnim, wrapAnim;
14770
14771         anchor = anchor || "t";
14772         obj = obj || {};
14773
14774         beforeAnim = function() {
14775             var animScope = this,
14776                 listeners = obj.listeners,
14777                 box, position, restoreSize, wrap, anim;
14778
14779             if (!slideOut) {
14780                 me.fixDisplay();
14781             }
14782
14783             box = me.getBox();
14784             if ((anchor == 't' || anchor == 'b') && box.height == 0) {
14785                 box.height = me.dom.scrollHeight;
14786             }
14787             else if ((anchor == 'l' || anchor == 'r') && box.width == 0) {
14788                 box.width = me.dom.scrollWidth;
14789             }
14790             
14791             position = me.getPositioning();
14792             me.setSize(box.width, box.height);
14793
14794             wrap = me.wrap({
14795                 style: {
14796                     visibility: slideOut ? 'visible' : 'hidden'
14797                 }
14798             });
14799             wrap.setPositioning(position);
14800             if (wrap.isStyle('position', 'static')) {
14801                 wrap.position('relative');
14802             }
14803             me.clearPositioning('auto');
14804             wrap.clip();
14805
14806             // This element is temporarily positioned absolute within its wrapper.
14807             // Restore to its default, CSS-inherited visibility setting.
14808             // We cannot explicitly poke visibility:visible into its style because that overrides the visibility of the wrap.
14809             me.setStyle({
14810                 visibility: '',
14811                 position: 'absolute'
14812             });
14813             if (slideOut) {
14814                 wrap.setSize(box.width, box.height);
14815             }
14816
14817             switch (anchor) {
14818                 case 't':
14819                     anim = {
14820                         from: {
14821                             width: box.width + 'px',
14822                             height: '0px'
14823                         },
14824                         to: {
14825                             width: box.width + 'px',
14826                             height: box.height + 'px'
14827                         }
14828                     };
14829                     elStyle.bottom = '0px';
14830                     break;
14831                 case 'l':
14832                     anim = {
14833                         from: {
14834                             width: '0px',
14835                             height: box.height + 'px'
14836                         },
14837                         to: {
14838                             width: box.width + 'px',
14839                             height: box.height + 'px'
14840                         }
14841                     };
14842                     elStyle.right = '0px';
14843                     break;
14844                 case 'r':
14845                     anim = {
14846                         from: {
14847                             x: box.x + box.width,
14848                             width: '0px',
14849                             height: box.height + 'px'
14850                         },
14851                         to: {
14852                             x: box.x,
14853                             width: box.width + 'px',
14854                             height: box.height + 'px'
14855                         }
14856                     };
14857                     break;
14858                 case 'b':
14859                     anim = {
14860                         from: {
14861                             y: box.y + box.height,
14862                             width: box.width + 'px',
14863                             height: '0px'
14864                         },
14865                         to: {
14866                             y: box.y,
14867                             width: box.width + 'px',
14868                             height: box.height + 'px'
14869                         }
14870                     };
14871                     break;
14872                 case 'tl':
14873                     anim = {
14874                         from: {
14875                             x: box.x,
14876                             y: box.y,
14877                             width: '0px',
14878                             height: '0px'
14879                         },
14880                         to: {
14881                             width: box.width + 'px',
14882                             height: box.height + 'px'
14883                         }
14884                     };
14885                     elStyle.bottom = '0px';
14886                     elStyle.right = '0px';
14887                     break;
14888                 case 'bl':
14889                     anim = {
14890                         from: {
14891                             x: box.x + box.width,
14892                             width: '0px',
14893                             height: '0px'
14894                         },
14895                         to: {
14896                             x: box.x,
14897                             width: box.width + 'px',
14898                             height: box.height + 'px'
14899                         }
14900                     };
14901                     elStyle.right = '0px';
14902                     break;
14903                 case 'br':
14904                     anim = {
14905                         from: {
14906                             x: box.x + box.width,
14907                             y: box.y + box.height,
14908                             width: '0px',
14909                             height: '0px'
14910                         },
14911                         to: {
14912                             x: box.x,
14913                             y: box.y,
14914                             width: box.width + 'px',
14915                             height: box.height + 'px'
14916                         }
14917                     };
14918                     break;
14919                 case 'tr':
14920                     anim = {
14921                         from: {
14922                             y: box.y + box.height,
14923                             width: '0px',
14924                             height: '0px'
14925                         },
14926                         to: {
14927                             y: box.y,
14928                             width: box.width + 'px',
14929                             height: box.height + 'px'
14930                         }
14931                     };
14932                     elStyle.bottom = '0px';
14933                     break;
14934             }
14935
14936             wrap.show();
14937             wrapAnim = Ext.apply({}, obj);
14938             delete wrapAnim.listeners;
14939             wrapAnim = Ext.create('Ext.fx.Anim', Ext.applyIf(wrapAnim, {
14940                 target: wrap,
14941                 duration: 500,
14942                 easing: 'ease-out',
14943                 from: slideOut ? anim.to : anim.from,
14944                 to: slideOut ? anim.from : anim.to
14945             }));
14946
14947             // In the absence of a callback, this listener MUST be added first
14948             wrapAnim.on('afteranimate', function() {
14949                 if (slideOut) {
14950                     me.setPositioning(position);
14951                     if (obj.useDisplay) {
14952                         me.setDisplayed(false);
14953                     } else {
14954                         me.hide();   
14955                     }
14956                 }
14957                 else {
14958                     me.clearPositioning();
14959                     me.setPositioning(position);
14960                 }
14961                 if (wrap.dom) {
14962                     wrap.dom.parentNode.insertBefore(me.dom, wrap.dom); 
14963                     wrap.remove();
14964                 }
14965                 me.setSize(box.width, box.height);
14966                 animScope.end();
14967             });
14968             // Add configured listeners after
14969             if (listeners) {
14970                 wrapAnim.on(listeners);
14971             }
14972         };
14973
14974         me.animate({
14975             duration: obj.duration ? obj.duration * 2 : 1000,
14976             listeners: {
14977                 beforeanimate: {
14978                     fn: beforeAnim
14979                 },
14980                 afteranimate: {
14981                     fn: function() {
14982                         if (wrapAnim && wrapAnim.running) {
14983                             wrapAnim.end();
14984                         }
14985                     }
14986                 }
14987             }
14988         });
14989         return me;
14990     },
14991
14992     
14993     /**
14994      * Slides the element out of view.  An anchor point can be optionally passed to set the end point
14995      * for the slide effect.  When the effect is completed, the element will be hidden (visibility = 
14996      * 'hidden') but block elements will still take up space in the document.  The element must be removed
14997      * from the DOM using the 'remove' config option if desired.  This function automatically handles 
14998      * wrapping the element with a fixed-size container if needed.  See the Fx class overview for valid anchor point options.
14999      * Usage:
15000      *<pre><code>
15001 // default: slide the element out to the top
15002 el.slideOut();
15003
15004 // custom: slide the element out to the right with a 2-second duration
15005 el.slideOut('r', { duration: 2 });
15006
15007 // common config options shown with default values
15008 el.slideOut('t', {
15009     easing: 'easeOut',
15010     duration: 500,
15011     remove: false,
15012     useDisplay: false
15013 });
15014 </code></pre>
15015      * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
15016      * @param {Object} options (optional) Object literal with any of the Fx config options
15017      * @return {Ext.core.Element} The Element
15018      */
15019     slideOut: function(anchor, o) {
15020         return this.slideIn(anchor, o, true);
15021     },
15022
15023     /**
15024      * Fades the element out while slowly expanding it in all directions.  When the effect is completed, the 
15025      * element will be hidden (visibility = 'hidden') but block elements will still take up space in the document.
15026      * Usage:
15027      *<pre><code>
15028 // default
15029 el.puff();
15030
15031 // common config options shown with default values
15032 el.puff({
15033     easing: 'easeOut',
15034     duration: 500,
15035     useDisplay: false
15036 });
15037 </code></pre>
15038      * @param {Object} options (optional) Object literal with any of the Fx config options
15039      * @return {Ext.core.Element} The Element
15040      */
15041
15042     puff: function(obj) {
15043         var me = this,
15044             beforeAnim;
15045         obj = Ext.applyIf(obj || {}, {
15046             easing: 'ease-out',
15047             duration: 500,
15048             useDisplay: false
15049         });
15050
15051         beforeAnim = function() {
15052             me.clearOpacity();
15053             me.show();
15054
15055             var box = me.getBox(),
15056                 fontSize = me.getStyle('fontSize'),
15057                 position = me.getPositioning();
15058             this.to = {
15059                 width: box.width * 2,
15060                 height: box.height * 2,
15061                 x: box.x - (box.width / 2),
15062                 y: box.y - (box.height /2),
15063                 opacity: 0,
15064                 fontSize: '200%'
15065             };
15066             this.on('afteranimate',function() {
15067                 if (me.dom) {
15068                     if (obj.useDisplay) {
15069                         me.setDisplayed(false);
15070                     } else {
15071                         me.hide();
15072                     }
15073                     me.clearOpacity();  
15074                     me.setPositioning(position);
15075                     me.setStyle({fontSize: fontSize});
15076                 }
15077             });
15078         };
15079
15080         me.animate({
15081             duration: obj.duration,
15082             easing: obj.easing,
15083             listeners: {
15084                 beforeanimate: {
15085                     fn: beforeAnim
15086                 }
15087             }
15088         });
15089         return me;
15090     },
15091
15092     /**
15093      * Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television).
15094      * When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still 
15095      * take up space in the document. The element must be removed from the DOM using the 'remove' config option if desired.
15096      * Usage:
15097      *<pre><code>
15098 // default
15099 el.switchOff();
15100
15101 // all config options shown with default values
15102 el.switchOff({
15103     easing: 'easeIn',
15104     duration: .3,
15105     remove: false,
15106     useDisplay: false
15107 });
15108 </code></pre>
15109      * @param {Object} options (optional) Object literal with any of the Fx config options
15110      * @return {Ext.core.Element} The Element
15111      */
15112     switchOff: function(obj) {
15113         var me = this,
15114             beforeAnim;
15115         
15116         obj = Ext.applyIf(obj || {}, {
15117             easing: 'ease-in',
15118             duration: 500,
15119             remove: false,
15120             useDisplay: false
15121         });
15122
15123         beforeAnim = function() {
15124             var animScope = this,
15125                 size = me.getSize(),
15126                 xy = me.getXY(),
15127                 keyframe, position;
15128             me.clearOpacity();
15129             me.clip();
15130             position = me.getPositioning();
15131
15132             keyframe = Ext.create('Ext.fx.Animator', {
15133                 target: me,
15134                 duration: obj.duration,
15135                 easing: obj.easing,
15136                 keyframes: {
15137                     33: {
15138                         opacity: 0.3
15139                     },
15140                     66: {
15141                         height: 1,
15142                         y: xy[1] + size.height / 2
15143                     },
15144                     100: {
15145                         width: 1,
15146                         x: xy[0] + size.width / 2
15147                     }
15148                 }
15149             });
15150             keyframe.on('afteranimate', function() {
15151                 if (obj.useDisplay) {
15152                     me.setDisplayed(false);
15153                 } else {
15154                     me.hide();
15155                 }  
15156                 me.clearOpacity();
15157                 me.setPositioning(position);
15158                 me.setSize(size);
15159                 animScope.end();
15160             });
15161         };
15162         me.animate({
15163             duration: (obj.duration * 2),
15164             listeners: {
15165                 beforeanimate: {
15166                     fn: beforeAnim
15167                 }
15168             }
15169         });
15170         return me;
15171     },
15172
15173    /**
15174     * Shows a ripple of exploding, attenuating borders to draw attention to an Element.
15175     * Usage:
15176 <pre><code>
15177 // default: a single light blue ripple
15178 el.frame();
15179
15180 // custom: 3 red ripples lasting 3 seconds total
15181 el.frame("#ff0000", 3, { duration: 3 });
15182
15183 // common config options shown with default values
15184 el.frame("#C3DAF9", 1, {
15185     duration: 1 //duration of each individual ripple.
15186     // Note: Easing is not configurable and will be ignored if included
15187 });
15188 </code></pre>
15189     * @param {String} color (optional) The color of the border.  Should be a 6 char hex color without the leading # (defaults to light blue: 'C3DAF9').
15190     * @param {Number} count (optional) The number of ripples to display (defaults to 1)
15191     * @param {Object} options (optional) Object literal with any of the Fx config options
15192     * @return {Ext.core.Element} The Element
15193     */
15194     frame : function(color, count, obj){
15195         var me = this,
15196             beforeAnim;
15197
15198         color = color || '#C3DAF9';
15199         count = count || 1;
15200         obj = obj || {};
15201
15202         beforeAnim = function() {
15203             me.show();
15204             var animScope = this,
15205                 box = me.getBox(),
15206                 proxy = Ext.getBody().createChild({
15207                     style: {
15208                         position : 'absolute',
15209                         'pointer-events': 'none',
15210                         'z-index': 35000,
15211                         border : '0px solid ' + color
15212                     }
15213                 }),
15214                 proxyAnim;
15215             proxyAnim = Ext.create('Ext.fx.Anim', {
15216                 target: proxy,
15217                 duration: obj.duration || 1000,
15218                 iterations: count,
15219                 from: {
15220                     top: box.y,
15221                     left: box.x,
15222                     borderWidth: 0,
15223                     opacity: 1,
15224                     height: box.height,
15225                     width: box.width
15226                 },
15227                 to: {
15228                     top: box.y - 20,
15229                     left: box.x - 20,
15230                     borderWidth: 10,
15231                     opacity: 0,
15232                     height: box.height + 40,
15233                     width: box.width + 40
15234                 }
15235             });
15236             proxyAnim.on('afteranimate', function() {
15237                 proxy.remove();
15238                 animScope.end();
15239             });
15240         };
15241
15242         me.animate({
15243             duration: (obj.duration * 2) || 2000,
15244             listeners: {
15245                 beforeanimate: {
15246                     fn: beforeAnim
15247                 }
15248             }
15249         });
15250         return me;
15251     },
15252
15253     /**
15254      * Slides the element while fading it out of view.  An anchor point can be optionally passed to set the 
15255      * ending point of the effect.
15256      * Usage:
15257      *<pre><code>
15258 // default: slide the element downward while fading out
15259 el.ghost();
15260
15261 // custom: slide the element out to the right with a 2-second duration
15262 el.ghost('r', { duration: 2 });
15263
15264 // common config options shown with default values
15265 el.ghost('b', {
15266     easing: 'easeOut',
15267     duration: 500
15268 });
15269 </code></pre>
15270      * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to bottom: 'b')
15271      * @param {Object} options (optional) Object literal with any of the Fx config options
15272      * @return {Ext.core.Element} The Element
15273      */
15274     ghost: function(anchor, obj) {
15275         var me = this,
15276             beforeAnim;
15277
15278         anchor = anchor || "b";
15279         beforeAnim = function() {
15280             var width = me.getWidth(),
15281                 height = me.getHeight(),
15282                 xy = me.getXY(),
15283                 position = me.getPositioning(),
15284                 to = {
15285                     opacity: 0
15286                 };
15287             switch (anchor) {
15288                 case 't':
15289                     to.y = xy[1] - height;
15290                     break;
15291                 case 'l':
15292                     to.x = xy[0] - width;
15293                     break;
15294                 case 'r':
15295                     to.x = xy[0] + width;
15296                     break;
15297                 case 'b':
15298                     to.y = xy[1] + height;
15299                     break;
15300                 case 'tl':
15301                     to.x = xy[0] - width;
15302                     to.y = xy[1] - height;
15303                     break;
15304                 case 'bl':
15305                     to.x = xy[0] - width;
15306                     to.y = xy[1] + height;
15307                     break;
15308                 case 'br':
15309                     to.x = xy[0] + width;
15310                     to.y = xy[1] + height;
15311                     break;
15312                 case 'tr':
15313                     to.x = xy[0] + width;
15314                     to.y = xy[1] - height;
15315                     break;
15316             }
15317             this.to = to;
15318             this.on('afteranimate', function () {
15319                 if (me.dom) {
15320                     me.hide();
15321                     me.clearOpacity();
15322                     me.setPositioning(position);
15323                 }
15324             });
15325         };
15326
15327         me.animate(Ext.applyIf(obj || {}, {
15328             duration: 500,
15329             easing: 'ease-out',
15330             listeners: {
15331                 beforeanimate: {
15332                     fn: beforeAnim
15333                 }
15334             }
15335         }));
15336         return me;
15337     },
15338
15339     /**
15340      * Highlights the Element by setting a color (applies to the background-color by default, but can be
15341      * changed using the "attr" config option) and then fading back to the original color. If no original
15342      * color is available, you should provide the "endColor" config option which will be cleared after the animation.
15343      * Usage:
15344 <pre><code>
15345 // default: highlight background to yellow
15346 el.highlight();
15347
15348 // custom: highlight foreground text to blue for 2 seconds
15349 el.highlight("0000ff", { attr: 'color', duration: 2 });
15350
15351 // common config options shown with default values
15352 el.highlight("ffff9c", {
15353     attr: "backgroundColor", //can be any valid CSS property (attribute) that supports a color value
15354     endColor: (current color) or "ffffff",
15355     easing: 'easeIn',
15356     duration: 1000
15357 });
15358 </code></pre>
15359      * @param {String} color (optional) The highlight color. Should be a 6 char hex color without the leading # (defaults to yellow: 'ffff9c')
15360      * @param {Object} options (optional) Object literal with any of the Fx config options
15361      * @return {Ext.core.Element} The Element
15362      */ 
15363     highlight: function(color, o) {
15364         var me = this,
15365             dom = me.dom,
15366             from = {},
15367             restore, to, attr, lns, event, fn;
15368
15369         o = o || {};
15370         lns = o.listeners || {};
15371         attr = o.attr || 'backgroundColor';
15372         from[attr] = color || 'ffff9c';
15373         
15374         if (!o.to) {
15375             to = {};
15376             to[attr] = o.endColor || me.getColor(attr, 'ffffff', '');
15377         }
15378         else {
15379             to = o.to;
15380         }
15381         
15382         // Don't apply directly on lns, since we reference it in our own callbacks below
15383         o.listeners = Ext.apply(Ext.apply({}, lns), {
15384             beforeanimate: function() {
15385                 restore = dom.style[attr];
15386                 me.clearOpacity();
15387                 me.show();
15388                 
15389                 event = lns.beforeanimate;
15390                 if (event) {
15391                     fn = event.fn || event;
15392                     return fn.apply(event.scope || lns.scope || window, arguments);
15393                 }
15394             },
15395             afteranimate: function() {
15396                 if (dom) {
15397                     dom.style[attr] = restore;
15398                 }
15399                 
15400                 event = lns.afteranimate;
15401                 if (event) {
15402                     fn = event.fn || event;
15403                     fn.apply(event.scope || lns.scope || window, arguments);
15404                 }
15405             }
15406         });
15407
15408         me.animate(Ext.apply({}, o, {
15409             duration: 1000,
15410             easing: 'ease-in',
15411             from: from,
15412             to: to
15413         }));
15414         return me;
15415     },
15416
15417    /**
15418     * @deprecated 4.0
15419     * Creates a pause before any subsequent queued effects begin.  If there are
15420     * no effects queued after the pause it will have no effect.
15421     * Usage:
15422 <pre><code>
15423 el.pause(1);
15424 </code></pre>
15425     * @param {Number} seconds The length of time to pause (in seconds)
15426     * @return {Ext.Element} The Element
15427     */
15428     pause: function(ms) {
15429         var me = this;
15430         Ext.fx.Manager.setFxDefaults(me.id, {
15431             delay: ms
15432         });
15433         return me;
15434     },
15435
15436    /**
15437     * Fade an element in (from transparent to opaque).  The ending opacity can be specified
15438     * using the <tt>{@link #endOpacity}</tt> config option.
15439     * Usage:
15440 <pre><code>
15441 // default: fade in from opacity 0 to 100%
15442 el.fadeIn();
15443
15444 // custom: fade in from opacity 0 to 75% over 2 seconds
15445 el.fadeIn({ endOpacity: .75, duration: 2});
15446
15447 // common config options shown with default values
15448 el.fadeIn({
15449     endOpacity: 1, //can be any value between 0 and 1 (e.g. .5)
15450     easing: 'easeOut',
15451     duration: 500
15452 });
15453 </code></pre>
15454     * @param {Object} options (optional) Object literal with any of the Fx config options
15455     * @return {Ext.Element} The Element
15456     */
15457     fadeIn: function(o) {
15458         this.animate(Ext.apply({}, o, {
15459             opacity: 1
15460         }));
15461         return this;
15462     },
15463
15464    /**
15465     * Fade an element out (from opaque to transparent).  The ending opacity can be specified
15466     * using the <tt>{@link #endOpacity}</tt> config option.  Note that IE may require
15467     * <tt>{@link #useDisplay}:true</tt> in order to redisplay correctly.
15468     * Usage:
15469 <pre><code>
15470 // default: fade out from the element's current opacity to 0
15471 el.fadeOut();
15472
15473 // custom: fade out from the element's current opacity to 25% over 2 seconds
15474 el.fadeOut({ endOpacity: .25, duration: 2});
15475
15476 // common config options shown with default values
15477 el.fadeOut({
15478     endOpacity: 0, //can be any value between 0 and 1 (e.g. .5)
15479     easing: 'easeOut',
15480     duration: 500,
15481     remove: false,
15482     useDisplay: false
15483 });
15484 </code></pre>
15485     * @param {Object} options (optional) Object literal with any of the Fx config options
15486     * @return {Ext.Element} The Element
15487     */
15488     fadeOut: function(o) {
15489         this.animate(Ext.apply({}, o, {
15490             opacity: 0
15491         }));
15492         return this;
15493     },
15494
15495    /**
15496     * @deprecated 4.0
15497     * Animates the transition of an element's dimensions from a starting height/width
15498     * to an ending height/width.  This method is a convenience implementation of {@link shift}.
15499     * Usage:
15500 <pre><code>
15501 // change height and width to 100x100 pixels
15502 el.scale(100, 100);
15503
15504 // common config options shown with default values.  The height and width will default to
15505 // the element&#39;s existing values if passed as null.
15506 el.scale(
15507     [element&#39;s width],
15508     [element&#39;s height], {
15509         easing: 'easeOut',
15510         duration: .35
15511     }
15512 );
15513 </code></pre>
15514     * @param {Number} width  The new width (pass undefined to keep the original width)
15515     * @param {Number} height  The new height (pass undefined to keep the original height)
15516     * @param {Object} options (optional) Object literal with any of the Fx config options
15517     * @return {Ext.Element} The Element
15518     */
15519     scale: function(w, h, o) {
15520         this.animate(Ext.apply({}, o, {
15521             width: w,
15522             height: h
15523         }));
15524         return this;
15525     },
15526
15527    /**
15528     * @deprecated 4.0
15529     * Animates the transition of any combination of an element's dimensions, xy position and/or opacity.
15530     * Any of these properties not specified in the config object will not be changed.  This effect 
15531     * requires that at least one new dimension, position or opacity setting must be passed in on
15532     * the config object in order for the function to have any effect.
15533     * Usage:
15534 <pre><code>
15535 // slide the element horizontally to x position 200 while changing the height and opacity
15536 el.shift({ x: 200, height: 50, opacity: .8 });
15537
15538 // common config options shown with default values.
15539 el.shift({
15540     width: [element&#39;s width],
15541     height: [element&#39;s height],
15542     x: [element&#39;s x position],
15543     y: [element&#39;s y position],
15544     opacity: [element&#39;s opacity],
15545     easing: 'easeOut',
15546     duration: .35
15547 });
15548 </code></pre>
15549     * @param {Object} options  Object literal with any of the Fx config options
15550     * @return {Ext.Element} The Element
15551     */
15552     shift: function(config) {
15553         this.animate(config);
15554         return this;
15555     }
15556 });
15557
15558 /**
15559  * @class Ext.core.Element
15560  */
15561 Ext.applyIf(Ext.core.Element, {
15562     unitRe: /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
15563     camelRe: /(-[a-z])/gi,
15564     opacityRe: /alpha\(opacity=(.*)\)/i,
15565     cssRe: /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
15566     propertyCache: {},
15567     defaultUnit : "px",
15568     borders: {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'},
15569     paddings: {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'},
15570     margins: {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'},
15571
15572     // Reference the prototype's version of the method. Signatures are identical.
15573     addUnits : Ext.core.Element.prototype.addUnits,
15574
15575     /**
15576      * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
15577      * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
15578      * @static
15579      * @param {Number|String} box The encoded margins
15580      * @return {Object} An object with margin sizes for top, right, bottom and left
15581      */
15582     parseBox : function(box) {
15583         if (Ext.isObject(box)) {
15584             return {
15585                 top: box.top || 0,
15586                 right: box.right || 0,
15587                 bottom: box.bottom || 0,
15588                 left: box.left || 0
15589             };
15590         } else {
15591             if (typeof box != 'string') {
15592                 box = box.toString();
15593             }
15594             var parts  = box.split(' '),
15595                 ln = parts.length;
15596     
15597             if (ln == 1) {
15598                 parts[1] = parts[2] = parts[3] = parts[0];
15599             }
15600             else if (ln == 2) {
15601                 parts[2] = parts[0];
15602                 parts[3] = parts[1];
15603             }
15604             else if (ln == 3) {
15605                 parts[3] = parts[1];
15606             }
15607     
15608             return {
15609                 top   :parseFloat(parts[0]) || 0,
15610                 right :parseFloat(parts[1]) || 0,
15611                 bottom:parseFloat(parts[2]) || 0,
15612                 left  :parseFloat(parts[3]) || 0
15613             };
15614         }
15615         
15616     },
15617     
15618     /**
15619      * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
15620      * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
15621      * @static
15622      * @param {Number|String} box The encoded margins
15623      * @param {String} units The type of units to add
15624      * @return {String} An string with unitized (px if units is not specified) metrics for top, right, bottom and left
15625      */
15626     unitizeBox : function(box, units) {
15627         var A = this.addUnits,
15628             B = this.parseBox(box);
15629             
15630         return A(B.top, units) + ' ' +
15631                A(B.right, units) + ' ' +
15632                A(B.bottom, units) + ' ' +
15633                A(B.left, units);
15634         
15635     },
15636
15637     // private
15638     camelReplaceFn : function(m, a) {
15639         return a.charAt(1).toUpperCase();
15640     },
15641
15642     /**
15643      * Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax.
15644      * For example:
15645      * <ul>
15646      *  <li>border-width -> borderWidth</li>
15647      *  <li>padding-top -> paddingTop</li>
15648      * </ul>
15649      * @static
15650      * @param {String} prop The property to normalize
15651      * @return {String} The normalized string
15652      */
15653     normalize : function(prop) {
15654         if (prop == 'float') {
15655             prop = Ext.supports.Float ? 'cssFloat' : 'styleFloat';
15656         }
15657         return this.propertyCache[prop] || (this.propertyCache[prop] = prop.replace(this.camelRe, this.camelReplaceFn));
15658     },
15659
15660     /**
15661      * Retrieves the document height
15662      * @static
15663      * @return {Number} documentHeight
15664      */
15665     getDocumentHeight: function() {
15666         return Math.max(!Ext.isStrict ? document.body.scrollHeight : document.documentElement.scrollHeight, this.getViewportHeight());
15667     },
15668
15669     /**
15670      * Retrieves the document width
15671      * @static
15672      * @return {Number} documentWidth
15673      */
15674     getDocumentWidth: function() {
15675         return Math.max(!Ext.isStrict ? document.body.scrollWidth : document.documentElement.scrollWidth, this.getViewportWidth());
15676     },
15677
15678     /**
15679      * Retrieves the viewport height of the window.
15680      * @static
15681      * @return {Number} viewportHeight
15682      */
15683     getViewportHeight: function(){
15684         return window.innerHeight;
15685     },
15686
15687     /**
15688      * Retrieves the viewport width of the window.
15689      * @static
15690      * @return {Number} viewportWidth
15691      */
15692     getViewportWidth : function() {
15693         return window.innerWidth;
15694     },
15695
15696     /**
15697      * Retrieves the viewport size of the window.
15698      * @static
15699      * @return {Object} object containing width and height properties
15700      */
15701     getViewSize : function() {
15702         return {
15703             width: window.innerWidth,
15704             height: window.innerHeight
15705         };
15706     },
15707
15708     /**
15709      * Retrieves the current orientation of the window. This is calculated by
15710      * determing if the height is greater than the width.
15711      * @static
15712      * @return {String} Orientation of window: 'portrait' or 'landscape'
15713      */
15714     getOrientation : function() {
15715         if (Ext.supports.OrientationChange) {
15716             return (window.orientation == 0) ? 'portrait' : 'landscape';
15717         }
15718         
15719         return (window.innerHeight > window.innerWidth) ? 'portrait' : 'landscape';
15720     },
15721
15722     /** 
15723      * Returns the top Element that is located at the passed coordinates
15724      * @static
15725      * @param {Number} x The x coordinate
15726      * @param {Number} x The y coordinate
15727      * @return {String} The found Element
15728      */
15729     fromPoint: function(x, y) {
15730         return Ext.get(document.elementFromPoint(x, y));
15731     },
15732     
15733     /**
15734      * Converts a CSS string into an object with a property for each style.
15735      * <p>
15736      * The sample code below would return an object with 2 properties, one
15737      * for background-color and one for color.</p>
15738      * <pre><code>
15739 var css = 'background-color: red;color: blue; ';
15740 console.log(Ext.core.Element.parseStyles(css));
15741      * </code></pre>
15742      * @static
15743      * @param {String} styles A CSS string
15744      * @return {Object} styles
15745      */
15746     parseStyles: function(styles){
15747         var out = {},
15748             cssRe = this.cssRe,
15749             matches;
15750             
15751         if (styles) {
15752             // Since we're using the g flag on the regex, we need to set the lastIndex.
15753             // This automatically happens on some implementations, but not others, see:
15754             // http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
15755             // http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
15756             cssRe.lastIndex = 0;
15757             while ((matches = cssRe.exec(styles))) {
15758                 out[matches[1]] = matches[2];
15759             }
15760         }
15761         return out;
15762     }
15763 });
15764
15765 /**
15766  * @class Ext.CompositeElementLite
15767  * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
15768  * members, or to perform collective actions upon the whole set.</p>
15769  * <p>Although they are not listed, this class supports all of the methods of {@link Ext.core.Element} and
15770  * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
15771  * Example:<pre><code>
15772 var els = Ext.select("#some-el div.some-class");
15773 // or select directly from an existing element
15774 var el = Ext.get('some-el');
15775 el.select('div.some-class');
15776
15777 els.setWidth(100); // all elements become 100 width
15778 els.hide(true); // all elements fade out and hide
15779 // or
15780 els.setWidth(100).hide(true);
15781 </code></pre>
15782  */
15783 Ext.CompositeElementLite = function(els, root){
15784     /**
15785      * <p>The Array of DOM elements which this CompositeElement encapsulates. Read-only.</p>
15786      * <p>This will not <i>usually</i> be accessed in developers' code, but developers wishing
15787      * to augment the capabilities of the CompositeElementLite class may use it when adding
15788      * methods to the class.</p>
15789      * <p>For example to add the <code>nextAll</code> method to the class to <b>add</b> all
15790      * following siblings of selected elements, the code would be</p><code><pre>
15791 Ext.override(Ext.CompositeElementLite, {
15792     nextAll: function() {
15793         var els = this.elements, i, l = els.length, n, r = [], ri = -1;
15794
15795 //      Loop through all elements in this Composite, accumulating
15796 //      an Array of all siblings.
15797         for (i = 0; i < l; i++) {
15798             for (n = els[i].nextSibling; n; n = n.nextSibling) {
15799                 r[++ri] = n;
15800             }
15801         }
15802
15803 //      Add all found siblings to this Composite
15804         return this.add(r);
15805     }
15806 });</pre></code>
15807      * @type Array
15808      * @property elements
15809      */
15810     this.elements = [];
15811     this.add(els, root);
15812     this.el = new Ext.core.Element.Flyweight();
15813 };
15814
15815 Ext.CompositeElementLite.prototype = {
15816     isComposite: true,
15817
15818     // private
15819     getElement : function(el){
15820         // Set the shared flyweight dom property to the current element
15821         var e = this.el;
15822         e.dom = el;
15823         e.id = el.id;
15824         return e;
15825     },
15826
15827     // private
15828     transformElement : function(el){
15829         return Ext.getDom(el);
15830     },
15831
15832     /**
15833      * Returns the number of elements in this Composite.
15834      * @return Number
15835      */
15836     getCount : function(){
15837         return this.elements.length;
15838     },
15839     /**
15840      * Adds elements to this Composite object.
15841      * @param {Mixed} els Either an Array of DOM elements to add, or another Composite object who's elements should be added.
15842      * @return {CompositeElement} This Composite object.
15843      */
15844     add : function(els, root){
15845         var me = this,
15846             elements = me.elements;
15847         if(!els){
15848             return this;
15849         }
15850         if(typeof els == "string"){
15851             els = Ext.core.Element.selectorFunction(els, root);
15852         }else if(els.isComposite){
15853             els = els.elements;
15854         }else if(!Ext.isIterable(els)){
15855             els = [els];
15856         }
15857
15858         for(var i = 0, len = els.length; i < len; ++i){
15859             elements.push(me.transformElement(els[i]));
15860         }
15861         return me;
15862     },
15863
15864     invoke : function(fn, args){
15865         var me = this,
15866             els = me.elements,
15867             len = els.length,
15868             e,
15869             i;
15870
15871         for(i = 0; i < len; i++) {
15872             e = els[i];
15873             if(e){
15874                 Ext.core.Element.prototype[fn].apply(me.getElement(e), args);
15875             }
15876         }
15877         return me;
15878     },
15879     /**
15880      * Returns a flyweight Element of the dom element object at the specified index
15881      * @param {Number} index
15882      * @return {Ext.core.Element}
15883      */
15884     item : function(index){
15885         var me = this,
15886             el = me.elements[index],
15887             out = null;
15888
15889         if(el){
15890             out = me.getElement(el);
15891         }
15892         return out;
15893     },
15894
15895     // fixes scope with flyweight
15896     addListener : function(eventName, handler, scope, opt){
15897         var els = this.elements,
15898             len = els.length,
15899             i, e;
15900
15901         for(i = 0; i<len; i++) {
15902             e = els[i];
15903             if(e) {
15904                 Ext.EventManager.on(e, eventName, handler, scope || e, opt);
15905             }
15906         }
15907         return this;
15908     },
15909     /**
15910      * <p>Calls the passed function for each element in this composite.</p>
15911      * @param {Function} fn The function to call. The function is passed the following parameters:<ul>
15912      * <li><b>el</b> : Element<div class="sub-desc">The current Element in the iteration.
15913      * <b>This is the flyweight (shared) Ext.core.Element instance, so if you require a
15914      * a reference to the dom node, use el.dom.</b></div></li>
15915      * <li><b>c</b> : Composite<div class="sub-desc">This Composite object.</div></li>
15916      * <li><b>idx</b> : Number<div class="sub-desc">The zero-based index in the iteration.</div></li>
15917      * </ul>
15918      * @param {Object} scope (optional) The scope (<i>this</i> reference) in which the function is executed. (defaults to the Element)
15919      * @return {CompositeElement} this
15920      */
15921     each : function(fn, scope){
15922         var me = this,
15923             els = me.elements,
15924             len = els.length,
15925             i, e;
15926
15927         for(i = 0; i<len; i++) {
15928             e = els[i];
15929             if(e){
15930                 e = this.getElement(e);
15931                 if(fn.call(scope || e, e, me, i) === false){
15932                     break;
15933                 }
15934             }
15935         }
15936         return me;
15937     },
15938
15939     /**
15940     * Clears this Composite and adds the elements passed.
15941     * @param {Mixed} els Either an array of DOM elements, or another Composite from which to fill this Composite.
15942     * @return {CompositeElement} this
15943     */
15944     fill : function(els){
15945         var me = this;
15946         me.elements = [];
15947         me.add(els);
15948         return me;
15949     },
15950
15951     /**
15952      * Filters this composite to only elements that match the passed selector.
15953      * @param {String/Function} selector A string CSS selector or a comparison function.
15954      * The comparison function will be called with the following arguments:<ul>
15955      * <li><code>el</code> : Ext.core.Element<div class="sub-desc">The current DOM element.</div></li>
15956      * <li><code>index</code> : Number<div class="sub-desc">The current index within the collection.</div></li>
15957      * </ul>
15958      * @return {CompositeElement} this
15959      */
15960     filter : function(selector){
15961         var els = [],
15962             me = this,
15963             fn = Ext.isFunction(selector) ? selector
15964                 : function(el){
15965                     return el.is(selector);
15966                 };
15967
15968         me.each(function(el, self, i) {
15969             if (fn(el, i) !== false) {
15970                 els[els.length] = me.transformElement(el);
15971             }
15972         });
15973         
15974         me.elements = els;
15975         return me;
15976     },
15977
15978     /**
15979      * Find the index of the passed element within the composite collection.
15980      * @param el {Mixed} The id of an element, or an Ext.core.Element, or an HtmlElement to find within the composite collection.
15981      * @return Number The index of the passed Ext.core.Element in the composite collection, or -1 if not found.
15982      */
15983     indexOf : function(el){
15984         return Ext.Array.indexOf(this.elements, this.transformElement(el));
15985     },
15986
15987     /**
15988     * Replaces the specified element with the passed element.
15989     * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
15990     * to replace.
15991     * @param {Mixed} replacement The id of an element or the Element itself.
15992     * @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
15993     * @return {CompositeElement} this
15994     */
15995     replaceElement : function(el, replacement, domReplace){
15996         var index = !isNaN(el) ? el : this.indexOf(el),
15997             d;
15998         if(index > -1){
15999             replacement = Ext.getDom(replacement);
16000             if(domReplace){
16001                 d = this.elements[index];
16002                 d.parentNode.insertBefore(replacement, d);
16003                 Ext.removeNode(d);
16004             }
16005             this.elements.splice(index, 1, replacement);
16006         }
16007         return this;
16008     },
16009
16010     /**
16011      * Removes all elements.
16012      */
16013     clear : function(){
16014         this.elements = [];
16015     }
16016 };
16017
16018 Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
16019
16020 /**
16021  * @private
16022  * Copies all of the functions from Ext.core.Element's prototype onto CompositeElementLite's prototype.
16023  * This is called twice - once immediately below, and once again after additional Ext.core.Element
16024  * are added in Ext JS
16025  */
16026 Ext.CompositeElementLite.importElementMethods = function() {
16027     var fnName,
16028         ElProto = Ext.core.Element.prototype,
16029         CelProto = Ext.CompositeElementLite.prototype;
16030
16031     for (fnName in ElProto) {
16032         if (typeof ElProto[fnName] == 'function'){
16033             (function(fnName) {
16034                 CelProto[fnName] = CelProto[fnName] || function() {
16035                     return this.invoke(fnName, arguments);
16036                 };
16037             }).call(CelProto, fnName);
16038
16039         }
16040     }
16041 };
16042
16043 Ext.CompositeElementLite.importElementMethods();
16044
16045 if(Ext.DomQuery){
16046     Ext.core.Element.selectorFunction = Ext.DomQuery.select;
16047 }
16048
16049 /**
16050  * Selects elements based on the passed CSS selector to enable {@link Ext.core.Element Element} methods
16051  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
16052  * {@link Ext.CompositeElementLite CompositeElementLite} object.
16053  * @param {String/Array} selector The CSS selector or an array of elements
16054  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
16055  * @return {CompositeElementLite/CompositeElement}
16056  * @member Ext.core.Element
16057  * @method select
16058  */
16059 Ext.core.Element.select = function(selector, root){
16060     var els;
16061     if(typeof selector == "string"){
16062         els = Ext.core.Element.selectorFunction(selector, root);
16063     }else if(selector.length !== undefined){
16064         els = selector;
16065     }else{
16066         Ext.Error.raise({
16067             sourceClass: "Ext.core.Element",
16068             sourceMethod: "select",
16069             selector: selector,
16070             root: root,
16071             msg: "Invalid selector specified: " + selector
16072         });
16073     }
16074     return new Ext.CompositeElementLite(els);
16075 };
16076 /**
16077  * Selects elements based on the passed CSS selector to enable {@link Ext.core.Element Element} methods
16078  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
16079  * {@link Ext.CompositeElementLite CompositeElementLite} object.
16080  * @param {String/Array} selector The CSS selector or an array of elements
16081  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
16082  * @return {CompositeElementLite/CompositeElement}
16083  * @member Ext
16084  * @method select
16085  */
16086 Ext.select = Ext.core.Element.select;
16087
16088 /**
16089  * @class Ext.util.DelayedTask
16090  * 
16091  * The DelayedTask class provides a convenient way to "buffer" the execution of a method,
16092  * performing setTimeout where a new timeout cancels the old timeout. When called, the
16093  * task will wait the specified time period before executing. If durng that time period,
16094  * the task is called again, the original call will be cancelled. This continues so that
16095  * the function is only called a single time for each iteration.
16096  * 
16097  * This method is especially useful for things like detecting whether a user has finished
16098  * typing in a text field. An example would be performing validation on a keypress. You can
16099  * use this class to buffer the keypress events for a certain number of milliseconds, and
16100  * perform only if they stop for that amount of time.  
16101  * 
16102  * ## Usage
16103  * 
16104  *     var task = new Ext.util.DelayedTask(function(){
16105  *         alert(Ext.getDom('myInputField').value.length);
16106  *     });
16107  *     
16108  *     // Wait 500ms before calling our function. If the user presses another key
16109  *     // during that 500ms, it will be cancelled and we'll wait another 500ms.
16110  *     Ext.get('myInputField').on('keypress', function(){
16111  *         task.{@link #delay}(500);
16112  *     });
16113  * 
16114  * Note that we are using a DelayedTask here to illustrate a point. The configuration
16115  * option `buffer` for {@link Ext.util.Observable#addListener addListener/on} will
16116  * also setup a delayed task for you to buffer events.
16117  * 
16118  * @constructor The parameters to this constructor serve as defaults and are not required.
16119  * @param {Function} fn (optional) The default function to call.
16120  * @param {Object} scope The default scope (The <code><b>this</b></code> reference) in which the
16121  * function is called. If not specified, <code>this</code> will refer to the browser window.
16122  * @param {Array} args (optional) The default Array of arguments.
16123  */
16124 Ext.util.DelayedTask = function(fn, scope, args) {
16125     var me = this,
16126         id,
16127         call = function() {
16128             clearInterval(id);
16129             id = null;
16130             fn.apply(scope, args || []);
16131         };
16132
16133     /**
16134      * Cancels any pending timeout and queues a new one
16135      * @param {Number} delay The milliseconds to delay
16136      * @param {Function} newFn (optional) Overrides function passed to constructor
16137      * @param {Object} newScope (optional) Overrides scope passed to constructor. Remember that if no scope
16138      * is specified, <code>this</code> will refer to the browser window.
16139      * @param {Array} newArgs (optional) Overrides args passed to constructor
16140      */
16141     this.delay = function(delay, newFn, newScope, newArgs) {
16142         me.cancel();
16143         fn = newFn || fn;
16144         scope = newScope || scope;
16145         args = newArgs || args;
16146         id = setInterval(call, delay);
16147     };
16148
16149     /**
16150      * Cancel the last queued timeout
16151      */
16152     this.cancel = function(){
16153         if (id) {
16154             clearInterval(id);
16155             id = null;
16156         }
16157     };
16158 };
16159 Ext.require('Ext.util.DelayedTask', function() {
16160
16161     Ext.util.Event = Ext.extend(Object, (function() {
16162         function createBuffered(handler, listener, o, scope) {
16163             listener.task = new Ext.util.DelayedTask();
16164             return function() {
16165                 listener.task.delay(o.buffer, handler, scope, Ext.Array.toArray(arguments));
16166             };
16167         }
16168
16169         function createDelayed(handler, listener, o, scope) {
16170             return function() {
16171                 var task = new Ext.util.DelayedTask();
16172                 if (!listener.tasks) {
16173                     listener.tasks = [];
16174                 }
16175                 listener.tasks.push(task);
16176                 task.delay(o.delay || 10, handler, scope, Ext.Array.toArray(arguments));
16177             };
16178         }
16179
16180         function createSingle(handler, listener, o, scope) {
16181             return function() {
16182                 listener.ev.removeListener(listener.fn, scope);
16183                 return handler.apply(scope, arguments);
16184             };
16185         }
16186
16187         return {
16188             isEvent: true,
16189
16190             constructor: function(observable, name) {
16191                 this.name = name;
16192                 this.observable = observable;
16193                 this.listeners = [];
16194             },
16195
16196             addListener: function(fn, scope, options) {
16197                 var me = this,
16198                     listener;
16199                     scope = scope || me.observable;
16200
16201                 if (!fn) {
16202                     Ext.Error.raise({
16203                         sourceClass: Ext.getClassName(this.observable),
16204                         sourceMethod: "addListener",
16205                         msg: "The specified callback function is undefined"
16206                     });
16207                 }
16208
16209                 if (!me.isListening(fn, scope)) {
16210                     listener = me.createListener(fn, scope, options);
16211                     if (me.firing) {
16212                         // if we are currently firing this event, don't disturb the listener loop
16213                         me.listeners = me.listeners.slice(0);
16214                     }
16215                     me.listeners.push(listener);
16216                 }
16217             },
16218
16219             createListener: function(fn, scope, o) {
16220                 o = o || {};
16221                 scope = scope || this.observable;
16222
16223                 var listener = {
16224                         fn: fn,
16225                         scope: scope,
16226                         o: o,
16227                         ev: this
16228                     },
16229                     handler = fn;
16230
16231                 // The order is important. The 'single' wrapper must be wrapped by the 'buffer' and 'delayed' wrapper
16232                 // because the event removal that the single listener does destroys the listener's DelayedTask(s)
16233                 if (o.single) {
16234                     handler = createSingle(handler, listener, o, scope);
16235                 }
16236                 if (o.delay) {
16237                     handler = createDelayed(handler, listener, o, scope);
16238                 }
16239                 if (o.buffer) {
16240                     handler = createBuffered(handler, listener, o, scope);
16241                 }
16242
16243                 listener.fireFn = handler;
16244                 return listener;
16245             },
16246
16247             findListener: function(fn, scope) {
16248                 var listeners = this.listeners,
16249                 i = listeners.length,
16250                 listener,
16251                 s;
16252
16253                 while (i--) {
16254                     listener = listeners[i];
16255                     if (listener) {
16256                         s = listener.scope;
16257                         if (listener.fn == fn && (s == scope || s == this.observable)) {
16258                             return i;
16259                         }
16260                     }
16261                 }
16262
16263                 return - 1;
16264             },
16265
16266             isListening: function(fn, scope) {
16267                 return this.findListener(fn, scope) !== -1;
16268             },
16269
16270             removeListener: function(fn, scope) {
16271                 var me = this,
16272                     index,
16273                     listener,
16274                     k;
16275                 index = me.findListener(fn, scope);
16276                 if (index != -1) {
16277                     listener = me.listeners[index];
16278
16279                     if (me.firing) {
16280                         me.listeners = me.listeners.slice(0);
16281                     }
16282
16283                     // cancel and remove a buffered handler that hasn't fired yet
16284                     if (listener.task) {
16285                         listener.task.cancel();
16286                         delete listener.task;
16287                     }
16288
16289                     // cancel and remove all delayed handlers that haven't fired yet
16290                     k = listener.tasks && listener.tasks.length;
16291                     if (k) {
16292                         while (k--) {
16293                             listener.tasks[k].cancel();
16294                         }
16295                         delete listener.tasks;
16296                     }
16297
16298                     // remove this listener from the listeners array
16299                     me.listeners.splice(index, 1);
16300                     return true;
16301                 }
16302
16303                 return false;
16304             },
16305
16306             // Iterate to stop any buffered/delayed events
16307             clearListeners: function() {
16308                 var listeners = this.listeners,
16309                     i = listeners.length;
16310
16311                 while (i--) {
16312                     this.removeListener(listeners[i].fn, listeners[i].scope);
16313                 }
16314             },
16315
16316             fire: function() {
16317                 var me = this,
16318                     listeners = me.listeners,
16319                     count = listeners.length,
16320                     i,
16321                     args,
16322                     listener;
16323
16324                 if (count > 0) {
16325                     me.firing = true;
16326                     for (i = 0; i < count; i++) {
16327                         listener = listeners[i];
16328                         args = arguments.length ? Array.prototype.slice.call(arguments, 0) : [];
16329                         if (listener.o) {
16330                             args.push(listener.o);
16331                         }
16332                         if (listener && listener.fireFn.apply(listener.scope || me.observable, args) === false) {
16333                             return (me.firing = false);
16334                         }
16335                     }
16336                 }
16337                 me.firing = false;
16338                 return true;
16339             }
16340         };
16341     })());
16342 });
16343
16344 /**
16345  * @class Ext.EventManager
16346  * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
16347  * several useful events directly.
16348  * See {@link Ext.EventObject} for more details on normalized event objects.
16349  * @singleton
16350  */
16351 Ext.EventManager = {
16352
16353     // --------------------- onReady ---------------------
16354
16355     /**
16356      * Check if we have bound our global onReady listener
16357      * @private
16358      */
16359     hasBoundOnReady: false,
16360
16361     /**
16362      * Check if fireDocReady has been called
16363      * @private
16364      */
16365     hasFiredReady: false,
16366
16367     /**
16368      * Timer for the document ready event in old IE versions
16369      * @private
16370      */
16371     readyTimeout: null,
16372
16373     /**
16374      * Checks if we have bound an onreadystatechange event
16375      * @private
16376      */
16377     hasOnReadyStateChange: false,
16378
16379     /**
16380      * Holds references to any onReady functions
16381      * @private
16382      */
16383     readyEvent: new Ext.util.Event(),
16384
16385     /**
16386      * Check the ready state for old IE versions
16387      * @private
16388      * @return {Boolean} True if the document is ready
16389      */
16390     checkReadyState: function(){
16391         var me = Ext.EventManager;
16392
16393         if(window.attachEvent){
16394             // See here for reference: http://javascript.nwbox.com/IEContentLoaded/
16395             if (window != top) {
16396                 return false;
16397             }
16398             try{
16399                 document.documentElement.doScroll('left');
16400             }catch(e){
16401                 return false;
16402             }
16403             me.fireDocReady();
16404             return true;
16405         }
16406         if (document.readyState == 'complete') {
16407             me.fireDocReady();
16408             return true;
16409         }
16410         me.readyTimeout = setTimeout(arguments.callee, 2);
16411         return false;
16412     },
16413
16414     /**
16415      * Binds the appropriate browser event for checking if the DOM has loaded.
16416      * @private
16417      */
16418     bindReadyEvent: function(){
16419         var me = Ext.EventManager;
16420         if (me.hasBoundOnReady) {
16421             return;
16422         }
16423
16424         if (document.addEventListener) {
16425             document.addEventListener('DOMContentLoaded', me.fireDocReady, false);
16426             // fallback, load will ~always~ fire
16427             window.addEventListener('load', me.fireDocReady, false);
16428         } else {
16429             // check if the document is ready, this will also kick off the scroll checking timer
16430             if (!me.checkReadyState()) {
16431                 document.attachEvent('onreadystatechange', me.checkReadyState);
16432                 me.hasOnReadyStateChange = true;
16433             }
16434             // fallback, onload will ~always~ fire
16435             window.attachEvent('onload', me.fireDocReady, false);
16436         }
16437         me.hasBoundOnReady = true;
16438     },
16439
16440     /**
16441      * We know the document is loaded, so trigger any onReady events.
16442      * @private
16443      */
16444     fireDocReady: function(){
16445         var me = Ext.EventManager;
16446
16447         // only unbind these events once
16448         if (!me.hasFiredReady) {
16449             me.hasFiredReady = true;
16450
16451             if (document.addEventListener) {
16452                 document.removeEventListener('DOMContentLoaded', me.fireDocReady, false);
16453                 window.removeEventListener('load', me.fireDocReady, false);
16454             } else {
16455                 if (me.readyTimeout !== null) {
16456                     clearTimeout(me.readyTimeout);
16457                 }
16458                 if (me.hasOnReadyStateChange) {
16459                     document.detachEvent('onreadystatechange', me.checkReadyState);
16460                 }
16461                 window.detachEvent('onload', me.fireDocReady);
16462             }
16463             Ext.supports.init();
16464         }
16465         if (!Ext.isReady) {
16466             Ext.isReady = true;
16467             me.onWindowUnload();
16468             me.readyEvent.fire();
16469         }
16470     },
16471
16472     /**
16473      * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
16474      * accessed shorthanded as Ext.onReady().
16475      * @param {Function} fn The method the event invokes.
16476      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
16477      * @param {boolean} options (optional) Options object as passed to {@link Ext.core.Element#addListener}.
16478      */
16479     onDocumentReady: function(fn, scope, options){
16480         options = options || {};
16481         var me = Ext.EventManager,
16482             readyEvent = me.readyEvent;
16483
16484         // force single to be true so our event is only ever fired once.
16485         options.single = true;
16486
16487         // Document already loaded, let's just fire it
16488         if (Ext.isReady) {
16489             readyEvent.addListener(fn, scope, options);
16490             readyEvent.fire();
16491         } else {
16492             options.delay = options.delay || 1;
16493             readyEvent.addListener(fn, scope, options);
16494             me.bindReadyEvent();
16495         }
16496     },
16497
16498
16499     // --------------------- event binding ---------------------
16500
16501     /**
16502      * Contains a list of all document mouse downs, so we can ensure they fire even when stopEvent is called.
16503      * @private
16504      */
16505     stoppedMouseDownEvent: new Ext.util.Event(),
16506
16507     /**
16508      * Options to parse for the 4th argument to addListener.
16509      * @private
16510      */
16511     propRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|freezeEvent)$/,
16512
16513     /**
16514      * Get the id of the element. If one has not been assigned, automatically assign it.
16515      * @param {Mixed} element The element to get the id for.
16516      * @return {String} id
16517      */
16518     getId : function(element) {
16519         var skipGarbageCollection = false,
16520             id;
16521     
16522         element = Ext.getDom(element);
16523     
16524         if (element === document || element === window) {
16525             id = element === document ? Ext.documentId : Ext.windowId;
16526         }
16527         else {
16528             id = Ext.id(element);
16529         }
16530         // skip garbage collection for special elements (window, document, iframes)
16531         if (element && (element.getElementById || element.navigator)) {
16532             skipGarbageCollection = true;
16533         }
16534     
16535         if (!Ext.cache[id]){
16536             Ext.core.Element.addToCache(new Ext.core.Element(element), id);
16537             if (skipGarbageCollection) {
16538                 Ext.cache[id].skipGarbageCollection = true;
16539             }
16540         }
16541         return id;
16542     },
16543
16544     /**
16545      * Convert a "config style" listener into a set of flat arguments so they can be passed to addListener
16546      * @private
16547      * @param {Object} element The element the event is for
16548      * @param {Object} event The event configuration
16549      * @param {Object} isRemove True if a removal should be performed, otherwise an add will be done.
16550      */
16551     prepareListenerConfig: function(element, config, isRemove){
16552         var me = this,
16553             propRe = me.propRe,
16554             key, value, args;
16555
16556         // loop over all the keys in the object
16557         for (key in config) {
16558             if (config.hasOwnProperty(key)) {
16559                 // if the key is something else then an event option
16560                 if (!propRe.test(key)) {
16561                     value = config[key];
16562                     // if the value is a function it must be something like click: function(){}, scope: this
16563                     // which means that there might be multiple event listeners with shared options
16564                     if (Ext.isFunction(value)) {
16565                         // shared options
16566                         args = [element, key, value, config.scope, config];
16567                     } else {
16568                         // if its not a function, it must be an object like click: {fn: function(){}, scope: this}
16569                         args = [element, key, value.fn, value.scope, value];
16570                     }
16571
16572                     if (isRemove === true) {
16573                         me.removeListener.apply(this, args);
16574                     } else {
16575                         me.addListener.apply(me, args);
16576                     }
16577                 }
16578             }
16579         }
16580     },
16581
16582     /**
16583      * Normalize cross browser event differences
16584      * @private
16585      * @param {Object} eventName The event name
16586      * @param {Object} fn The function to execute
16587      * @return {Object} The new event name/function
16588      */
16589     normalizeEvent: function(eventName, fn){
16590         if (/mouseenter|mouseleave/.test(eventName) && !Ext.supports.MouseEnterLeave) {
16591             if (fn) {
16592                 fn = Ext.Function.createInterceptor(fn, this.contains, this);
16593             }
16594             eventName = eventName == 'mouseenter' ? 'mouseover' : 'mouseout';
16595         } else if (eventName == 'mousewheel' && !Ext.supports.MouseWheel && !Ext.isOpera){
16596             eventName = 'DOMMouseScroll';
16597         }
16598         return {
16599             eventName: eventName,
16600             fn: fn
16601         };
16602     },
16603
16604     /**
16605      * Checks whether the event's relatedTarget is contained inside (or <b>is</b>) the element.
16606      * @private
16607      * @param {Object} event
16608      */
16609     contains: function(event){
16610         var parent = event.browserEvent.currentTarget,
16611             child = this.getRelatedTarget(event);
16612
16613         if (parent && parent.firstChild) {
16614             while (child) {
16615                 if (child === parent) {
16616                     return false;
16617                 }
16618                 child = child.parentNode;
16619                 if (child && (child.nodeType != 1)) {
16620                     child = null;
16621                 }
16622             }
16623         }
16624         return true;
16625     },
16626
16627     /**
16628     * Appends an event handler to an element.  The shorthand version {@link #on} is equivalent.  Typically you will
16629     * use {@link Ext.core.Element#addListener} directly on an Element in favor of calling this version.
16630     * @param {String/HTMLElement} el The html element or id to assign the event handler to.
16631     * @param {String} eventName The name of the event to listen for.
16632     * @param {Function} handler The handler function the event invokes. This function is passed
16633     * the following parameters:<ul>
16634     * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
16635     * <li>t : Element<div class="sub-desc">The {@link Ext.core.Element Element} which was the target of the event.
16636     * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
16637     * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
16638     * </ul>
16639     * @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>.
16640     * @param {Object} options (optional) An object containing handler configuration properties.
16641     * This may contain any of the following properties:<ul>
16642     * <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>
16643     * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
16644     * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
16645     * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
16646     * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
16647     * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
16648     * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
16649     * <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>
16650     * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
16651     * by the specified number of milliseconds. If the event fires again within that time, the original
16652     * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
16653     * <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>
16654     * </ul><br>
16655     * <p>See {@link Ext.core.Element#addListener} for examples of how to use these options.</p>
16656     */
16657     addListener: function(element, eventName, fn, scope, options){
16658         // Check if we've been passed a "config style" event.
16659         if (Ext.isObject(eventName)) {
16660             this.prepareListenerConfig(element, eventName);
16661             return;
16662         }
16663
16664         var dom = Ext.getDom(element),
16665             bind,
16666             wrap;
16667
16668         if (!dom){
16669             Ext.Error.raise({
16670                 sourceClass: 'Ext.EventManager',
16671                 sourceMethod: 'addListener',
16672                 targetElement: element,
16673                 eventName: eventName,
16674                 msg: 'Error adding "' + eventName + '\" listener for nonexistent element "' + element + '"'
16675             });
16676         }
16677         if (!fn) {
16678             Ext.Error.raise({
16679                 sourceClass: 'Ext.EventManager',
16680                 sourceMethod: 'addListener',
16681                 targetElement: element,
16682                 eventName: eventName,
16683                 msg: 'Error adding "' + eventName + '\" listener. The handler function is undefined.'
16684             });
16685         }
16686
16687         // create the wrapper function
16688         options = options || {};
16689
16690         bind = this.normalizeEvent(eventName, fn);
16691         wrap = this.createListenerWrap(dom, eventName, bind.fn, scope, options);
16692
16693
16694         if (dom.attachEvent) {
16695             dom.attachEvent('on' + bind.eventName, wrap);
16696         } else {
16697             dom.addEventListener(bind.eventName, wrap, options.capture || false);
16698         }
16699
16700         if (dom == document && eventName == 'mousedown') {
16701             this.stoppedMouseDownEvent.addListener(wrap);
16702         }
16703
16704         // add all required data into the event cache
16705         this.getEventListenerCache(dom, eventName).push({
16706             fn: fn,
16707             wrap: wrap,
16708             scope: scope
16709         });
16710     },
16711
16712     /**
16713     * Removes an event handler from an element.  The shorthand version {@link #un} is equivalent.  Typically
16714     * you will use {@link Ext.core.Element#removeListener} directly on an Element in favor of calling this version.
16715     * @param {String/HTMLElement} el The id or html element from which to remove the listener.
16716     * @param {String} eventName The name of the event.
16717     * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
16718     * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
16719     * then this must refer to the same object.
16720     */
16721     removeListener : function(element, eventName, fn, scope) {
16722         // handle our listener config object syntax
16723         if (Ext.isObject(eventName)) {
16724             this.prepareListenerConfig(element, eventName, true);
16725             return;
16726         }
16727
16728         var dom = Ext.getDom(element),
16729             cache = this.getEventListenerCache(dom, eventName),
16730             bindName = this.normalizeEvent(eventName).eventName,
16731             i = cache.length, j,
16732             listener, wrap, tasks;
16733
16734
16735         while (i--) {
16736             listener = cache[i];
16737
16738             if (listener && (!fn || listener.fn == fn) && (!scope || listener.scope === scope)) {
16739                 wrap = listener.wrap;
16740
16741                 // clear buffered calls
16742                 if (wrap.task) {
16743                     clearTimeout(wrap.task);
16744                     delete wrap.task;
16745                 }
16746
16747                 // clear delayed calls
16748                 j = wrap.tasks && wrap.tasks.length;
16749                 if (j) {
16750                     while (j--) {
16751                         clearTimeout(wrap.tasks[j]);
16752                     }
16753                     delete wrap.tasks;
16754                 }
16755
16756                 if (dom.detachEvent) {
16757                     dom.detachEvent('on' + bindName, wrap);
16758                 } else {
16759                     dom.removeEventListener(bindName, wrap, false);
16760                 }
16761
16762                 if (wrap && dom == document && eventName == 'mousedown') {
16763                     this.stoppedMouseDownEvent.removeListener(wrap);
16764                 }
16765
16766                 // remove listener from cache
16767                 cache.splice(i, 1);
16768             }
16769         }
16770     },
16771
16772     /**
16773     * Removes all event handers from an element.  Typically you will use {@link Ext.core.Element#removeAllListeners}
16774     * directly on an Element in favor of calling this version.
16775     * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
16776     */
16777     removeAll : function(element){
16778         var dom = Ext.getDom(element),
16779             cache, ev;
16780         if (!dom) {
16781             return;
16782         }
16783         cache = this.getElementEventCache(dom);
16784
16785         for (ev in cache) {
16786             if (cache.hasOwnProperty(ev)) {
16787                 this.removeListener(dom, ev);
16788             }
16789         }
16790         Ext.cache[dom.id].events = {};
16791     },
16792
16793     /**
16794      * Recursively removes all previous added listeners from an element and its children. Typically you will use {@link Ext.core.Element#purgeAllListeners}
16795      * directly on an Element in favor of calling this version.
16796      * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
16797      * @param {String} eventName (optional) The name of the event.
16798      */
16799     purgeElement : function(element, eventName) {
16800         var dom = Ext.getDom(element),
16801             i = 0, len;
16802
16803         if(eventName) {
16804             this.removeListener(dom, eventName);
16805         }
16806         else {
16807             this.removeAll(dom);
16808         }
16809
16810         if(dom && dom.childNodes) {
16811             for(len = element.childNodes.length; i < len; i++) {
16812                 this.purgeElement(element.childNodes[i], eventName);
16813             }
16814         }
16815     },
16816
16817     /**
16818      * Create the wrapper function for the event
16819      * @private
16820      * @param {HTMLElement} dom The dom element
16821      * @param {String} ename The event name
16822      * @param {Function} fn The function to execute
16823      * @param {Object} scope The scope to execute callback in
16824      * @param {Object} options The options
16825      * @return {Function} the wrapper function
16826      */
16827     createListenerWrap : function(dom, ename, fn, scope, options) {
16828         options = !Ext.isObject(options) ? {} : options;
16829
16830         var f, gen;
16831
16832         return function wrap(e, args) {
16833             // Compile the implementation upon first firing
16834             if (!gen) {
16835                 f = ['if(!Ext) {return;}'];
16836
16837                 if(options.buffer || options.delay || options.freezeEvent) {
16838                     f.push('e = new Ext.EventObjectImpl(e, ' + (options.freezeEvent ? 'true' : 'false' ) + ');');
16839                 } else {
16840                     f.push('e = Ext.EventObject.setEvent(e);');
16841                 }
16842
16843                 if (options.delegate) {
16844                     f.push('var t = e.getTarget("' + options.delegate + '", this);');
16845                     f.push('if(!t) {return;}');
16846                 } else {
16847                     f.push('var t = e.target;');
16848                 }
16849
16850                 if (options.target) {
16851                     f.push('if(e.target !== options.target) {return;}');
16852                 }
16853
16854                 if(options.stopEvent) {
16855                     f.push('e.stopEvent();');
16856                 } else {
16857                     if(options.preventDefault) {
16858                         f.push('e.preventDefault();');
16859                     }
16860                     if(options.stopPropagation) {
16861                         f.push('e.stopPropagation();');
16862                     }
16863                 }
16864
16865                 if(options.normalized === false) {
16866                     f.push('e = e.browserEvent;');
16867                 }
16868
16869                 if(options.buffer) {
16870                     f.push('(wrap.task && clearTimeout(wrap.task));');
16871                     f.push('wrap.task = setTimeout(function(){');
16872                 }
16873
16874                 if(options.delay) {
16875                     f.push('wrap.tasks = wrap.tasks || [];');
16876                     f.push('wrap.tasks.push(setTimeout(function(){');
16877                 }
16878
16879                 // finally call the actual handler fn
16880                 f.push('fn.call(scope || dom, e, t, options);');
16881
16882                 if(options.single) {
16883                     f.push('Ext.EventManager.removeListener(dom, ename, fn, scope);');
16884                 }
16885
16886                 if(options.delay) {
16887                     f.push('}, ' + options.delay + '));');
16888                 }
16889
16890                 if(options.buffer) {
16891                     f.push('}, ' + options.buffer + ');');
16892                 }
16893
16894                 gen = Ext.functionFactory('e', 'options', 'fn', 'scope', 'ename', 'dom', 'wrap', 'args', f.join('\n'));
16895             }
16896
16897             gen.call(dom, e, options, fn, scope, ename, dom, wrap, args);
16898         };
16899     },
16900
16901     /**
16902      * Get the event cache for a particular element for a particular event
16903      * @private
16904      * @param {HTMLElement} element The element
16905      * @param {Object} eventName The event name
16906      * @return {Array} The events for the element
16907      */
16908     getEventListenerCache : function(element, eventName) {
16909         var eventCache = this.getElementEventCache(element);
16910         return eventCache[eventName] || (eventCache[eventName] = []);
16911     },
16912
16913     /**
16914      * Gets the event cache for the object
16915      * @private
16916      * @param {HTMLElement} element The element
16917      * @return {Object} The event cache for the object
16918      */
16919     getElementEventCache : function(element) {
16920         var elementCache = Ext.cache[this.getId(element)];
16921         return elementCache.events || (elementCache.events = {});
16922     },
16923
16924     // --------------------- utility methods ---------------------
16925     mouseLeaveRe: /(mouseout|mouseleave)/,
16926     mouseEnterRe: /(mouseover|mouseenter)/,
16927
16928     /**
16929      * Stop the event (preventDefault and stopPropagation)
16930      * @param {Event} The event to stop
16931      */
16932     stopEvent: function(event) {
16933         this.stopPropagation(event);
16934         this.preventDefault(event);
16935     },
16936
16937     /**
16938      * Cancels bubbling of the event.
16939      * @param {Event} The event to stop bubbling.
16940      */
16941     stopPropagation: function(event) {
16942         event = event.browserEvent || event;
16943         if (event.stopPropagation) {
16944             event.stopPropagation();
16945         } else {
16946             event.cancelBubble = true;
16947         }
16948     },
16949
16950     /**
16951      * Prevents the browsers default handling of the event.
16952      * @param {Event} The event to prevent the default
16953      */
16954     preventDefault: function(event) {
16955         event = event.browserEvent || event;
16956         if (event.preventDefault) {
16957             event.preventDefault();
16958         } else {
16959             event.returnValue = false;
16960             // Some keys events require setting the keyCode to -1 to be prevented
16961             try {
16962               // all ctrl + X and F1 -> F12
16963               if (event.ctrlKey || event.keyCode > 111 && event.keyCode < 124) {
16964                   event.keyCode = -1;
16965               }
16966             } catch (e) {
16967                 // see this outdated document http://support.microsoft.com/kb/934364/en-us for more info
16968             }
16969         }
16970     },
16971
16972     /**
16973      * Gets the related target from the event.
16974      * @param {Object} event The event
16975      * @return {HTMLElement} The related target.
16976      */
16977     getRelatedTarget: function(event) {
16978         event = event.browserEvent || event;
16979         var target = event.relatedTarget;
16980         if (!target) {
16981             if (this.mouseLeaveRe.test(event.type)) {
16982                 target = event.toElement;
16983             } else if (this.mouseEnterRe.test(event.type)) {
16984                 target = event.fromElement;
16985             }
16986         }
16987         return this.resolveTextNode(target);
16988     },
16989
16990     /**
16991      * Gets the x coordinate from the event
16992      * @param {Object} event The event
16993      * @return {Number} The x coordinate
16994      */
16995     getPageX: function(event) {
16996         return this.getXY(event)[0];
16997     },
16998
16999     /**
17000      * Gets the y coordinate from the event
17001      * @param {Object} event The event
17002      * @return {Number} The y coordinate
17003      */
17004     getPageY: function(event) {
17005         return this.getXY(event)[1];
17006     },
17007
17008     /**
17009      * Gets the x & ycoordinate from the event
17010      * @param {Object} event The event
17011      * @return {Array} The x/y coordinate
17012      */
17013     getPageXY: function(event) {
17014         event = event.browserEvent || event;
17015         var x = event.pageX,
17016             y = event.pageY,
17017             doc = document.documentElement,
17018             body = document.body;
17019
17020         // pageX/pageY not available (undefined, not null), use clientX/clientY instead
17021         if (!x && x !== 0) {
17022             x = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
17023             y = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
17024         }
17025         return [x, y];
17026     },
17027
17028     /**
17029      * Gets the target of the event.
17030      * @param {Object} event The event
17031      * @return {HTMLElement} target
17032      */
17033     getTarget: function(event) {
17034         event = event.browserEvent || event;
17035         return this.resolveTextNode(event.target || event.srcElement);
17036     },
17037
17038     /**
17039      * Resolve any text nodes accounting for browser differences.
17040      * @private
17041      * @param {HTMLElement} node The node
17042      * @return {HTMLElement} The resolved node
17043      */
17044     // 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.
17045     resolveTextNode: Ext.isGecko ?
17046         function(node) {
17047             if (!node) {
17048                 return;
17049             }
17050             // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197
17051             var s = HTMLElement.prototype.toString.call(node);
17052             if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') {
17053                 return;
17054             }
17055                 return node.nodeType == 3 ? node.parentNode: node;
17056             }: function(node) {
17057                 return node && node.nodeType == 3 ? node.parentNode: node;
17058             },
17059
17060     // --------------------- custom event binding ---------------------
17061
17062     // Keep track of the current width/height
17063     curWidth: 0,
17064     curHeight: 0,
17065
17066     /**
17067      * Adds a listener to be notified when the browser window is resized and provides resize event buffering (100 milliseconds),
17068      * passes new viewport width and height to handlers.
17069      * @param {Function} fn      The handler function the window resize event invokes.
17070      * @param {Object}   scope   The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
17071      * @param {boolean}  options Options object as passed to {@link Ext.core.Element#addListener}
17072      */
17073     onWindowResize: function(fn, scope, options){
17074         var resize = this.resizeEvent;
17075         if(!resize){
17076             this.resizeEvent = resize = new Ext.util.Event();
17077             this.on(window, 'resize', this.fireResize, this, {buffer: 100});
17078         }
17079         resize.addListener(fn, scope, options);
17080     },
17081
17082     /**
17083      * Fire the resize event.
17084      * @private
17085      */
17086     fireResize: function(){
17087         var me = this,
17088             w = Ext.core.Element.getViewWidth(),
17089             h = Ext.core.Element.getViewHeight();
17090
17091          //whacky problem in IE where the resize event will sometimes fire even though the w/h are the same.
17092          if(me.curHeight != h || me.curWidth != w){
17093              me.curHeight = h;
17094              me.curWidth = w;
17095              me.resizeEvent.fire(w, h);
17096          }
17097     },
17098
17099     /**
17100      * Removes the passed window resize listener.
17101      * @param {Function} fn        The method the event invokes
17102      * @param {Object}   scope    The scope of handler
17103      */
17104     removeResizeListener: function(fn, scope){
17105         if (this.resizeEvent) {
17106             this.resizeEvent.removeListener(fn, scope);
17107         }
17108     },
17109
17110     onWindowUnload: function() {
17111         var unload = this.unloadEvent;
17112         if (!unload) {
17113             this.unloadEvent = unload = new Ext.util.Event();
17114             this.addListener(window, 'unload', this.fireUnload, this);
17115         }
17116     },
17117
17118     /**
17119      * Fires the unload event for items bound with onWindowUnload
17120      * @private
17121      */
17122     fireUnload: function() {
17123         // wrap in a try catch, could have some problems during unload
17124         try {
17125             this.removeUnloadListener();
17126             // Work around FF3 remembering the last scroll position when refreshing the grid and then losing grid view
17127             if (Ext.isGecko3) {
17128                 var gridviews = Ext.ComponentQuery.query('gridview'),
17129                     i = 0,
17130                     ln = gridviews.length;
17131                 for (; i < ln; i++) {
17132                     gridviews[i].scrollToTop();
17133                 }
17134             }
17135             // Purge all elements in the cache
17136             var el,
17137                 cache = Ext.cache;
17138             for (el in cache) {
17139                 if (cache.hasOwnProperty(el)) {
17140                     Ext.EventManager.removeAll(el);
17141                 }
17142             }
17143         } catch(e) {
17144         }
17145     },
17146
17147     /**
17148      * Removes the passed window unload listener.
17149      * @param {Function} fn        The method the event invokes
17150      * @param {Object}   scope    The scope of handler
17151      */
17152     removeUnloadListener: function(){
17153         if (this.unloadEvent) {
17154             this.removeListener(window, 'unload', this.fireUnload);
17155         }
17156     },
17157
17158     /**
17159      * note 1: IE fires ONLY the keydown event on specialkey autorepeat
17160      * note 2: Safari < 3.1, Gecko (Mac/Linux) & Opera fire only the keypress event on specialkey autorepeat
17161      * (research done by @Jan Wolter at http://unixpapa.com/js/key.html)
17162      * @private
17163      */
17164     useKeyDown: Ext.isWebKit ?
17165                    parseInt(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1], 10) >= 525 :
17166                    !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera),
17167
17168     /**
17169      * Indicates which event to use for getting key presses.
17170      * @return {String} The appropriate event name.
17171      */
17172     getKeyEvent: function(){
17173         return this.useKeyDown ? 'keydown' : 'keypress';
17174     }
17175 };
17176
17177 /**
17178  * Alias for {@link Ext.Loader#onReady Ext.Loader.onReady} with withDomReady set to true
17179  * @member Ext
17180  * @method onReady
17181  */
17182 Ext.onReady = function(fn, scope, options) {
17183     Ext.Loader.onReady(fn, scope, true, options);
17184 };
17185
17186 /**
17187  * Alias for {@link Ext.EventManager#onDocumentReady Ext.EventManager.onDocumentReady}
17188  * @member Ext
17189  * @method onDocumentReady
17190  */
17191 Ext.onDocumentReady = Ext.EventManager.onDocumentReady;
17192
17193 /**
17194  * Alias for {@link Ext.EventManager#addListener Ext.EventManager.addListener}
17195  * @member Ext.EventManager
17196  * @method on
17197  */
17198 Ext.EventManager.on = Ext.EventManager.addListener;
17199
17200 /**
17201  * Alias for {@link Ext.EventManager#removeListener Ext.EventManager.removeListener}
17202  * @member Ext.EventManager
17203  * @method un
17204  */
17205 Ext.EventManager.un = Ext.EventManager.removeListener;
17206
17207 (function(){
17208     var initExtCss = function() {
17209         // find the body element
17210         var bd = document.body || document.getElementsByTagName('body')[0],
17211             baseCSSPrefix = Ext.baseCSSPrefix,
17212             cls = [],
17213             htmlCls = [],
17214             html;
17215
17216         if (!bd) {
17217             return false;
17218         }
17219
17220         html = bd.parentNode;
17221
17222         //Let's keep this human readable!
17223         if (Ext.isIE) {
17224             cls.push(baseCSSPrefix + 'ie');
17225         }
17226         if (Ext.isIE6) {
17227             cls.push(baseCSSPrefix + 'ie6');
17228         }
17229         if (Ext.isIE7) {
17230             cls.push(baseCSSPrefix + 'ie7');
17231         }
17232         if (Ext.isIE8) {
17233             cls.push(baseCSSPrefix + 'ie8');
17234         }
17235         if (Ext.isIE9) {
17236             cls.push(baseCSSPrefix + 'ie9');
17237         }
17238         if (Ext.isGecko) {
17239             cls.push(baseCSSPrefix + 'gecko');
17240         }
17241         if (Ext.isGecko3) {
17242             cls.push(baseCSSPrefix + 'gecko3');
17243         }
17244         if (Ext.isGecko4) {
17245             cls.push(baseCSSPrefix + 'gecko4');
17246         }
17247         if (Ext.isOpera) {
17248             cls.push(baseCSSPrefix + 'opera');
17249         }
17250         if (Ext.isWebKit) {
17251             cls.push(baseCSSPrefix + 'webkit');
17252         }
17253         if (Ext.isSafari) {
17254             cls.push(baseCSSPrefix + 'safari');
17255         }
17256         if (Ext.isSafari2) {
17257             cls.push(baseCSSPrefix + 'safari2');
17258         }
17259         if (Ext.isSafari3) {
17260             cls.push(baseCSSPrefix + 'safari3');
17261         }
17262         if (Ext.isSafari4) {
17263             cls.push(baseCSSPrefix + 'safari4');
17264         }
17265         if (Ext.isChrome) {
17266             cls.push(baseCSSPrefix + 'chrome');
17267         }
17268         if (Ext.isMac) {
17269             cls.push(baseCSSPrefix + 'mac');
17270         }
17271         if (Ext.isLinux) {
17272             cls.push(baseCSSPrefix + 'linux');
17273         }
17274         if (!Ext.supports.CSS3BorderRadius) {
17275             cls.push(baseCSSPrefix + 'nbr');
17276         }
17277         if (!Ext.supports.CSS3LinearGradient) {
17278             cls.push(baseCSSPrefix + 'nlg');
17279         }
17280         if (!Ext.scopeResetCSS) {
17281             cls.push(baseCSSPrefix + 'reset');
17282         }
17283
17284         // add to the parent to allow for selectors x-strict x-border-box, also set the isBorderBox property correctly
17285         if (html) {
17286             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
17287                 Ext.isBorderBox = false;
17288             }
17289             else {
17290                 Ext.isBorderBox = true;
17291             }
17292
17293             htmlCls.push(baseCSSPrefix + (Ext.isBorderBox ? 'border-box' : 'strict'));
17294             if (!Ext.isStrict) {
17295                 htmlCls.push(baseCSSPrefix + 'quirks');
17296                 if (Ext.isIE && !Ext.isStrict) {
17297                     Ext.isIEQuirks = true;
17298                 }
17299             }
17300             Ext.fly(html, '_internal').addCls(htmlCls);
17301         }
17302
17303         Ext.fly(bd, '_internal').addCls(cls);
17304         return true;
17305     };
17306
17307     Ext.onReady(initExtCss);
17308 })();
17309
17310 /**
17311  * @class Ext.EventObject
17312
17313 Just as {@link Ext.core.Element} wraps around a native DOM node, Ext.EventObject
17314 wraps the browser's native event-object normalizing cross-browser differences,
17315 such as which mouse button is clicked, keys pressed, mechanisms to stop
17316 event-propagation along with a method to prevent default actions from taking place.
17317
17318 For example:
17319
17320     function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
17321         e.preventDefault();
17322         var target = e.getTarget(); // same as t (the target HTMLElement)
17323         ...
17324     }
17325
17326     var myDiv = {@link Ext#get Ext.get}("myDiv");  // get reference to an {@link Ext.core.Element}
17327     myDiv.on(         // 'on' is shorthand for addListener
17328         "click",      // perform an action on click of myDiv
17329         handleClick   // reference to the action handler
17330     );
17331
17332     // other methods to do the same:
17333     Ext.EventManager.on("myDiv", 'click', handleClick);
17334     Ext.EventManager.addListener("myDiv", 'click', handleClick);
17335
17336  * @singleton
17337  * @markdown
17338  */
17339 Ext.define('Ext.EventObjectImpl', {
17340     uses: ['Ext.util.Point'],
17341
17342     /** Key constant @type Number */
17343     BACKSPACE: 8,
17344     /** Key constant @type Number */
17345     TAB: 9,
17346     /** Key constant @type Number */
17347     NUM_CENTER: 12,
17348     /** Key constant @type Number */
17349     ENTER: 13,
17350     /** Key constant @type Number */
17351     RETURN: 13,
17352     /** Key constant @type Number */
17353     SHIFT: 16,
17354     /** Key constant @type Number */
17355     CTRL: 17,
17356     /** Key constant @type Number */
17357     ALT: 18,
17358     /** Key constant @type Number */
17359     PAUSE: 19,
17360     /** Key constant @type Number */
17361     CAPS_LOCK: 20,
17362     /** Key constant @type Number */
17363     ESC: 27,
17364     /** Key constant @type Number */
17365     SPACE: 32,
17366     /** Key constant @type Number */
17367     PAGE_UP: 33,
17368     /** Key constant @type Number */
17369     PAGE_DOWN: 34,
17370     /** Key constant @type Number */
17371     END: 35,
17372     /** Key constant @type Number */
17373     HOME: 36,
17374     /** Key constant @type Number */
17375     LEFT: 37,
17376     /** Key constant @type Number */
17377     UP: 38,
17378     /** Key constant @type Number */
17379     RIGHT: 39,
17380     /** Key constant @type Number */
17381     DOWN: 40,
17382     /** Key constant @type Number */
17383     PRINT_SCREEN: 44,
17384     /** Key constant @type Number */
17385     INSERT: 45,
17386     /** Key constant @type Number */
17387     DELETE: 46,
17388     /** Key constant @type Number */
17389     ZERO: 48,
17390     /** Key constant @type Number */
17391     ONE: 49,
17392     /** Key constant @type Number */
17393     TWO: 50,
17394     /** Key constant @type Number */
17395     THREE: 51,
17396     /** Key constant @type Number */
17397     FOUR: 52,
17398     /** Key constant @type Number */
17399     FIVE: 53,
17400     /** Key constant @type Number */
17401     SIX: 54,
17402     /** Key constant @type Number */
17403     SEVEN: 55,
17404     /** Key constant @type Number */
17405     EIGHT: 56,
17406     /** Key constant @type Number */
17407     NINE: 57,
17408     /** Key constant @type Number */
17409     A: 65,
17410     /** Key constant @type Number */
17411     B: 66,
17412     /** Key constant @type Number */
17413     C: 67,
17414     /** Key constant @type Number */
17415     D: 68,
17416     /** Key constant @type Number */
17417     E: 69,
17418     /** Key constant @type Number */
17419     F: 70,
17420     /** Key constant @type Number */
17421     G: 71,
17422     /** Key constant @type Number */
17423     H: 72,
17424     /** Key constant @type Number */
17425     I: 73,
17426     /** Key constant @type Number */
17427     J: 74,
17428     /** Key constant @type Number */
17429     K: 75,
17430     /** Key constant @type Number */
17431     L: 76,
17432     /** Key constant @type Number */
17433     M: 77,
17434     /** Key constant @type Number */
17435     N: 78,
17436     /** Key constant @type Number */
17437     O: 79,
17438     /** Key constant @type Number */
17439     P: 80,
17440     /** Key constant @type Number */
17441     Q: 81,
17442     /** Key constant @type Number */
17443     R: 82,
17444     /** Key constant @type Number */
17445     S: 83,
17446     /** Key constant @type Number */
17447     T: 84,
17448     /** Key constant @type Number */
17449     U: 85,
17450     /** Key constant @type Number */
17451     V: 86,
17452     /** Key constant @type Number */
17453     W: 87,
17454     /** Key constant @type Number */
17455     X: 88,
17456     /** Key constant @type Number */
17457     Y: 89,
17458     /** Key constant @type Number */
17459     Z: 90,
17460     /** Key constant @type Number */
17461     CONTEXT_MENU: 93,
17462     /** Key constant @type Number */
17463     NUM_ZERO: 96,
17464     /** Key constant @type Number */
17465     NUM_ONE: 97,
17466     /** Key constant @type Number */
17467     NUM_TWO: 98,
17468     /** Key constant @type Number */
17469     NUM_THREE: 99,
17470     /** Key constant @type Number */
17471     NUM_FOUR: 100,
17472     /** Key constant @type Number */
17473     NUM_FIVE: 101,
17474     /** Key constant @type Number */
17475     NUM_SIX: 102,
17476     /** Key constant @type Number */
17477     NUM_SEVEN: 103,
17478     /** Key constant @type Number */
17479     NUM_EIGHT: 104,
17480     /** Key constant @type Number */
17481     NUM_NINE: 105,
17482     /** Key constant @type Number */
17483     NUM_MULTIPLY: 106,
17484     /** Key constant @type Number */
17485     NUM_PLUS: 107,
17486     /** Key constant @type Number */
17487     NUM_MINUS: 109,
17488     /** Key constant @type Number */
17489     NUM_PERIOD: 110,
17490     /** Key constant @type Number */
17491     NUM_DIVISION: 111,
17492     /** Key constant @type Number */
17493     F1: 112,
17494     /** Key constant @type Number */
17495     F2: 113,
17496     /** Key constant @type Number */
17497     F3: 114,
17498     /** Key constant @type Number */
17499     F4: 115,
17500     /** Key constant @type Number */
17501     F5: 116,
17502     /** Key constant @type Number */
17503     F6: 117,
17504     /** Key constant @type Number */
17505     F7: 118,
17506     /** Key constant @type Number */
17507     F8: 119,
17508     /** Key constant @type Number */
17509     F9: 120,
17510     /** Key constant @type Number */
17511     F10: 121,
17512     /** Key constant @type Number */
17513     F11: 122,
17514     /** Key constant @type Number */
17515     F12: 123,
17516
17517     /**
17518      * Simple click regex
17519      * @private
17520      */
17521     clickRe: /(dbl)?click/,
17522     // safari keypress events for special keys return bad keycodes
17523     safariKeys: {
17524         3: 13, // enter
17525         63234: 37, // left
17526         63235: 39, // right
17527         63232: 38, // up
17528         63233: 40, // down
17529         63276: 33, // page up
17530         63277: 34, // page down
17531         63272: 46, // delete
17532         63273: 36, // home
17533         63275: 35 // end
17534     },
17535     // normalize button clicks, don't see any way to feature detect this.
17536     btnMap: Ext.isIE ? {
17537         1: 0,
17538         4: 1,
17539         2: 2
17540     } : {
17541         0: 0,
17542         1: 1,
17543         2: 2
17544     },
17545
17546     constructor: function(event, freezeEvent){
17547         if (event) {
17548             this.setEvent(event.browserEvent || event, freezeEvent);
17549         }
17550     },
17551
17552     setEvent: function(event, freezeEvent){
17553         var me = this, button, options;
17554
17555         if (event == me || (event && event.browserEvent)) { // already wrapped
17556             return event;
17557         }
17558         me.browserEvent = event;
17559         if (event) {
17560             // normalize buttons
17561             button = event.button ? me.btnMap[event.button] : (event.which ? event.which - 1 : -1);
17562             if (me.clickRe.test(event.type) && button == -1) {
17563                 button = 0;
17564             }
17565             options = {
17566                 type: event.type,
17567                 button: button,
17568                 shiftKey: event.shiftKey,
17569                 // mac metaKey behaves like ctrlKey
17570                 ctrlKey: event.ctrlKey || event.metaKey || false,
17571                 altKey: event.altKey,
17572                 // in getKey these will be normalized for the mac
17573                 keyCode: event.keyCode,
17574                 charCode: event.charCode,
17575                 // cache the targets for the delayed and or buffered events
17576                 target: Ext.EventManager.getTarget(event),
17577                 relatedTarget: Ext.EventManager.getRelatedTarget(event),
17578                 currentTarget: event.currentTarget,
17579                 xy: (freezeEvent ? me.getXY() : null)
17580             };
17581         } else {
17582             options = {
17583                 button: -1,
17584                 shiftKey: false,
17585                 ctrlKey: false,
17586                 altKey: false,
17587                 keyCode: 0,
17588                 charCode: 0,
17589                 target: null,
17590                 xy: [0, 0]
17591             };
17592         }
17593         Ext.apply(me, options);
17594         return me;
17595     },
17596
17597     /**
17598      * Stop the event (preventDefault and stopPropagation)
17599      */
17600     stopEvent: function(){
17601         this.stopPropagation();
17602         this.preventDefault();
17603     },
17604
17605     /**
17606      * Prevents the browsers default handling of the event.
17607      */
17608     preventDefault: function(){
17609         if (this.browserEvent) {
17610             Ext.EventManager.preventDefault(this.browserEvent);
17611         }
17612     },
17613
17614     /**
17615      * Cancels bubbling of the event.
17616      */
17617     stopPropagation: function(){
17618         var browserEvent = this.browserEvent;
17619
17620         if (browserEvent) {
17621             if (browserEvent.type == 'mousedown') {
17622                 Ext.EventManager.stoppedMouseDownEvent.fire(this);
17623             }
17624             Ext.EventManager.stopPropagation(browserEvent);
17625         }
17626     },
17627
17628     /**
17629      * Gets the character code for the event.
17630      * @return {Number}
17631      */
17632     getCharCode: function(){
17633         return this.charCode || this.keyCode;
17634     },
17635
17636     /**
17637      * Returns a normalized keyCode for the event.
17638      * @return {Number} The key code
17639      */
17640     getKey: function(){
17641         return this.normalizeKey(this.keyCode || this.charCode);
17642     },
17643
17644     /**
17645      * Normalize key codes across browsers
17646      * @private
17647      * @param {Number} key The key code
17648      * @return {Number} The normalized code
17649      */
17650     normalizeKey: function(key){
17651         // can't feature detect this
17652         return Ext.isWebKit ? (this.safariKeys[key] || key) : key;
17653     },
17654
17655     /**
17656      * Gets the x coordinate of the event.
17657      * @return {Number}
17658      * @deprecated 4.0 Replaced by {@link #getX}
17659      */
17660     getPageX: function(){
17661         return this.getX();
17662     },
17663
17664     /**
17665      * Gets the y coordinate of the event.
17666      * @return {Number}
17667      * @deprecated 4.0 Replaced by {@link #getY}
17668      */
17669     getPageY: function(){
17670         return this.getY();
17671     },
17672     
17673     /**
17674      * Gets the x coordinate of the event.
17675      * @return {Number}
17676      */
17677     getX: function() {
17678         return this.getXY()[0];
17679     },    
17680     
17681     /**
17682      * Gets the y coordinate of the event.
17683      * @return {Number}
17684      */
17685     getY: function() {
17686         return this.getXY()[1];
17687     },
17688         
17689     /**
17690      * Gets the page coordinates of the event.
17691      * @return {Array} The xy values like [x, y]
17692      */
17693     getXY: function() {
17694         if (!this.xy) {
17695             // same for XY
17696             this.xy = Ext.EventManager.getPageXY(this.browserEvent);
17697         }
17698         return this.xy;
17699     },
17700
17701     /**
17702      * Gets the target for the event.
17703      * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
17704      * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
17705      * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
17706      * @return {HTMLelement}
17707      */
17708     getTarget : function(selector, maxDepth, returnEl){
17709         if (selector) {
17710             return Ext.fly(this.target).findParent(selector, maxDepth, returnEl);
17711         }
17712         return returnEl ? Ext.get(this.target) : this.target;
17713     },
17714
17715     /**
17716      * Gets the related target.
17717      * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
17718      * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
17719      * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
17720      * @return {HTMLElement}
17721      */
17722     getRelatedTarget : function(selector, maxDepth, returnEl){
17723         if (selector) {
17724             return Ext.fly(this.relatedTarget).findParent(selector, maxDepth, returnEl);
17725         }
17726         return returnEl ? Ext.get(this.relatedTarget) : this.relatedTarget;
17727     },
17728
17729     /**
17730      * Normalizes mouse wheel delta across browsers
17731      * @return {Number} The delta
17732      */
17733     getWheelDelta : function(){
17734         var event = this.browserEvent,
17735             delta = 0;
17736
17737         if (event.wheelDelta) { /* IE/Opera. */
17738             delta = event.wheelDelta / 120;
17739         } else if (event.detail){ /* Mozilla case. */
17740             delta = -event.detail / 3;
17741         }
17742         return delta;
17743     },
17744
17745     /**
17746     * 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.
17747     * Example usage:<pre><code>
17748 // Handle click on any child of an element
17749 Ext.getBody().on('click', function(e){
17750     if(e.within('some-el')){
17751         alert('Clicked on a child of some-el!');
17752     }
17753 });
17754
17755 // Handle click directly on an element, ignoring clicks on child nodes
17756 Ext.getBody().on('click', function(e,t){
17757     if((t.id == 'some-el') && !e.within(t, true)){
17758         alert('Clicked directly on some-el!');
17759     }
17760 });
17761 </code></pre>
17762      * @param {Mixed} el The id, DOM element or Ext.core.Element to check
17763      * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
17764      * @param {Boolean} allowEl {optional} true to also check if the passed element is the target or related target
17765      * @return {Boolean}
17766      */
17767     within : function(el, related, allowEl){
17768         if(el){
17769             var t = related ? this.getRelatedTarget() : this.getTarget(),
17770                 result;
17771
17772             if (t) {
17773                 result = Ext.fly(el).contains(t);
17774                 if (!result && allowEl) {
17775                     result = t == Ext.getDom(el);
17776                 }
17777                 return result;
17778             }
17779         }
17780         return false;
17781     },
17782
17783     /**
17784      * Checks if the key pressed was a "navigation" key
17785      * @return {Boolean} True if the press is a navigation keypress
17786      */
17787     isNavKeyPress : function(){
17788         var me = this,
17789             k = this.normalizeKey(me.keyCode);
17790
17791        return (k >= 33 && k <= 40) ||  // Page Up/Down, End, Home, Left, Up, Right, Down
17792        k == me.RETURN ||
17793        k == me.TAB ||
17794        k == me.ESC;
17795     },
17796
17797     /**
17798      * Checks if the key pressed was a "special" key
17799      * @return {Boolean} True if the press is a special keypress
17800      */
17801     isSpecialKey : function(){
17802         var k = this.normalizeKey(this.keyCode);
17803         return (this.type == 'keypress' && this.ctrlKey) ||
17804         this.isNavKeyPress() ||
17805         (k == this.BACKSPACE) || // Backspace
17806         (k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
17807         (k >= 44 && k <= 46);   // Print Screen, Insert, Delete
17808     },
17809
17810     /**
17811      * Returns a point object that consists of the object coordinates.
17812      * @return {Ext.util.Point} point
17813      */
17814     getPoint : function(){
17815         var xy = this.getXY();
17816         return Ext.create('Ext.util.Point', xy[0], xy[1]);
17817     },
17818
17819    /**
17820     * Returns true if the control, meta, shift or alt key was pressed during this event.
17821     * @return {Boolean}
17822     */
17823     hasModifier : function(){
17824         return this.ctrlKey || this.altKey || this.shiftKey || this.metaKey;
17825     },
17826
17827     /**
17828      * Injects a DOM event using the data in this object and (optionally) a new target.
17829      * This is a low-level technique and not likely to be used by application code. The
17830      * currently supported event types are:
17831      * <p><b>HTMLEvents</b></p>
17832      * <ul>
17833      * <li>load</li>
17834      * <li>unload</li>
17835      * <li>select</li>
17836      * <li>change</li>
17837      * <li>submit</li>
17838      * <li>reset</li>
17839      * <li>resize</li>
17840      * <li>scroll</li>
17841      * </ul>
17842      * <p><b>MouseEvents</b></p>
17843      * <ul>
17844      * <li>click</li>
17845      * <li>dblclick</li>
17846      * <li>mousedown</li>
17847      * <li>mouseup</li>
17848      * <li>mouseover</li>
17849      * <li>mousemove</li>
17850      * <li>mouseout</li>
17851      * </ul>
17852      * <p><b>UIEvents</b></p>
17853      * <ul>
17854      * <li>focusin</li>
17855      * <li>focusout</li>
17856      * <li>activate</li>
17857      * <li>focus</li>
17858      * <li>blur</li>
17859      * </ul>
17860      * @param {Element/HTMLElement} target If specified, the target for the event. This
17861      * is likely to be used when relaying a DOM event. If not specified, {@link #getTarget}
17862      * is used to determine the target.
17863      */
17864     injectEvent: function () {
17865         var API,
17866             dispatchers = {}; // keyed by event type (e.g., 'mousedown')
17867
17868         // Good reference: http://developer.yahoo.com/yui/docs/UserAction.js.html
17869
17870         // IE9 has createEvent, but this code causes major problems with htmleditor (it
17871         // blocks all mouse events and maybe more). TODO
17872
17873         if (!Ext.isIE && document.createEvent) { // if (DOM compliant)
17874             API = {
17875                 createHtmlEvent: function (doc, type, bubbles, cancelable) {
17876                     var event = doc.createEvent('HTMLEvents');
17877
17878                     event.initEvent(type, bubbles, cancelable);
17879                     return event;
17880                 },
17881
17882                 createMouseEvent: function (doc, type, bubbles, cancelable, detail,
17883                                             clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
17884                                             button, relatedTarget) {
17885                     var event = doc.createEvent('MouseEvents'),
17886                         view = doc.defaultView || window;
17887
17888                     if (event.initMouseEvent) {
17889                         event.initMouseEvent(type, bubbles, cancelable, view, detail,
17890                                     clientX, clientY, clientX, clientY, ctrlKey, altKey,
17891                                     shiftKey, metaKey, button, relatedTarget);
17892                     } else { // old Safari
17893                         event = doc.createEvent('UIEvents');
17894                         event.initEvent(type, bubbles, cancelable);
17895                         event.view = view;
17896                         event.detail = detail;
17897                         event.screenX = clientX;
17898                         event.screenY = clientY;
17899                         event.clientX = clientX;
17900                         event.clientY = clientY;
17901                         event.ctrlKey = ctrlKey;
17902                         event.altKey = altKey;
17903                         event.metaKey = metaKey;
17904                         event.shiftKey = shiftKey;
17905                         event.button = button;
17906                         event.relatedTarget = relatedTarget;
17907                     }
17908
17909                     return event;
17910                 },
17911
17912                 createUIEvent: function (doc, type, bubbles, cancelable, detail) {
17913                     var event = doc.createEvent('UIEvents'),
17914                         view = doc.defaultView || window;
17915
17916                     event.initUIEvent(type, bubbles, cancelable, view, detail);
17917                     return event;
17918                 },
17919
17920                 fireEvent: function (target, type, event) {
17921                     target.dispatchEvent(event);
17922                 },
17923
17924                 fixTarget: function (target) {
17925                     // Safari3 doesn't have window.dispatchEvent()
17926                     if (target == window && !target.dispatchEvent) {
17927                         return document;
17928                     }
17929
17930                     return target;
17931                 }
17932             }
17933         } else if (document.createEventObject) { // else if (IE)
17934             var crazyIEButtons = { 0: 1, 1: 4, 2: 2 };
17935
17936             API = {
17937                 createHtmlEvent: function (doc, type, bubbles, cancelable) {
17938                     var event = doc.createEventObject();
17939                     event.bubbles = bubbles;
17940                     event.cancelable = cancelable;
17941                     return event;
17942                 },
17943
17944                 createMouseEvent: function (doc, type, bubbles, cancelable, detail,
17945                                             clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
17946                                             button, relatedTarget) {
17947                     var event = doc.createEventObject();
17948                     event.bubbles = bubbles;
17949                     event.cancelable = cancelable;
17950                     event.detail = detail;
17951                     event.screenX = clientX;
17952                     event.screenY = clientY;
17953                     event.clientX = clientX;
17954                     event.clientY = clientY;
17955                     event.ctrlKey = ctrlKey;
17956                     event.altKey = altKey;
17957                     event.shiftKey = shiftKey;
17958                     event.metaKey = metaKey;
17959                     event.button = crazyIEButtons[button] || button;
17960                     event.relatedTarget = relatedTarget; // cannot assign to/fromElement
17961                     return event;
17962                 },
17963
17964                 createUIEvent: function (doc, type, bubbles, cancelable, detail) {
17965                     var event = doc.createEventObject();
17966                     event.bubbles = bubbles;
17967                     event.cancelable = cancelable;
17968                     return event;
17969                 },
17970
17971                 fireEvent: function (target, type, event) {
17972                     target.fireEvent('on' + type, event);
17973                 },
17974
17975                 fixTarget: function (target) {
17976                     if (target == document) {
17977                         // IE6,IE7 thinks window==document and doesn't have window.fireEvent()
17978                         // IE6,IE7 cannot properly call document.fireEvent()
17979                         return document.documentElement;
17980                     }
17981
17982                     return target;
17983                 }
17984             };
17985         }
17986
17987         //----------------
17988         // HTMLEvents
17989
17990         Ext.Object.each({
17991                 load:   [false, false],
17992                 unload: [false, false],
17993                 select: [true, false],
17994                 change: [true, false],
17995                 submit: [true, true],
17996                 reset:  [true, false],
17997                 resize: [true, false],
17998                 scroll: [true, false]
17999             },
18000             function (name, value) {
18001                 var bubbles = value[0], cancelable = value[1];
18002                 dispatchers[name] = function (targetEl, srcEvent) {
18003                     var e = API.createHtmlEvent(name, bubbles, cancelable);
18004                     API.fireEvent(targetEl, name, e);
18005                 };
18006             });
18007
18008         //----------------
18009         // MouseEvents
18010
18011         function createMouseEventDispatcher (type, detail) {
18012             var cancelable = (type != 'mousemove');
18013             return function (targetEl, srcEvent) {
18014                 var xy = srcEvent.getXY(),
18015                     e = API.createMouseEvent(targetEl.ownerDocument, type, true, cancelable,
18016                                 detail, xy[0], xy[1], srcEvent.ctrlKey, srcEvent.altKey,
18017                                 srcEvent.shiftKey, srcEvent.metaKey, srcEvent.button,
18018                                 srcEvent.relatedTarget);
18019                 API.fireEvent(targetEl, type, e);
18020             };
18021         }
18022
18023         Ext.each(['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout'],
18024             function (eventName) {
18025                 dispatchers[eventName] = createMouseEventDispatcher(eventName, 1);
18026             });
18027
18028         //----------------
18029         // UIEvents
18030
18031         Ext.Object.each({
18032                 focusin:  [true, false],
18033                 focusout: [true, false],
18034                 activate: [true, true],
18035                 focus:    [false, false],
18036                 blur:     [false, false]
18037             },
18038             function (name, value) {
18039                 var bubbles = value[0], cancelable = value[1];
18040                 dispatchers[name] = function (targetEl, srcEvent) {
18041                     var e = API.createUIEvent(targetEl.ownerDocument, name, bubbles, cancelable, 1);
18042                     API.fireEvent(targetEl, name, e);
18043                 };
18044             });
18045
18046         //---------
18047         if (!API) {
18048             // not even sure what ancient browsers fall into this category...
18049
18050             dispatchers = {}; // never mind all those we just built :P
18051
18052             API = {
18053                 fixTarget: function (t) {
18054                     return t;
18055                 }
18056             };
18057         }
18058
18059         function cannotInject (target, srcEvent) {
18060             // TODO log something
18061         }
18062
18063         return function (target) {
18064             var me = this,
18065                 dispatcher = dispatchers[me.type] || cannotInject,
18066                 t = target ? (target.dom || target) : me.getTarget();
18067
18068             t = API.fixTarget(t);
18069             dispatcher(t, me);
18070         };
18071     }() // call to produce method
18072
18073 }, function() {
18074
18075 Ext.EventObject = new Ext.EventObjectImpl();
18076
18077 });
18078
18079
18080 /**
18081  * @class Ext.core.Element
18082  */
18083 (function(){
18084     var doc = document,
18085         activeElement = null,
18086         isCSS1 = doc.compatMode == "CSS1Compat",
18087         ELEMENT = Ext.core.Element,
18088         fly = function(el){
18089             if (!_fly) {
18090                 _fly = new Ext.core.Element.Flyweight();
18091             }
18092             _fly.dom = el;
18093             return _fly;
18094         }, _fly;
18095
18096     // If the browser does not support document.activeElement we need some assistance.
18097     // This covers old Safari 3.2 (4.0 added activeElement along with just about all
18098     // other browsers). We need this support to handle issues with old Safari.
18099     if (!('activeElement' in doc) && doc.addEventListener) {
18100         doc.addEventListener('focus',
18101             function (ev) {
18102                 if (ev && ev.target) {
18103                     activeElement = (ev.target == doc) ? null : ev.target;
18104                 }
18105             }, true);
18106     }
18107
18108     /*
18109      * Helper function to create the function that will restore the selection.
18110      */
18111     function makeSelectionRestoreFn (activeEl, start, end) {
18112         return function () {
18113             activeEl.selectionStart = start;
18114             activeEl.selectionEnd = end;
18115         };
18116     }
18117
18118     Ext.apply(ELEMENT, {
18119         isAncestor : function(p, c) {
18120             var ret = false;
18121
18122             p = Ext.getDom(p);
18123             c = Ext.getDom(c);
18124             if (p && c) {
18125                 if (p.contains) {
18126                     return p.contains(c);
18127                 } else if (p.compareDocumentPosition) {
18128                     return !!(p.compareDocumentPosition(c) & 16);
18129                 } else {
18130                     while ((c = c.parentNode)) {
18131                         ret = c == p || ret;
18132                     }
18133                 }
18134             }
18135             return ret;
18136         },
18137
18138         /**
18139          * Returns the active element in the DOM. If the browser supports activeElement
18140          * on the document, this is returned. If not, the focus is tracked and the active
18141          * element is maintained internally.
18142          * @return {HTMLElement} The active (focused) element in the document.
18143          */
18144         getActiveElement: function () {
18145             return doc.activeElement || activeElement;
18146         },
18147
18148         /**
18149          * Creates a function to call to clean up problems with the work-around for the
18150          * WebKit RightMargin bug. The work-around is to add "display: 'inline-block'" to
18151          * the element before calling getComputedStyle and then to restore its original
18152          * display value. The problem with this is that it corrupts the selection of an
18153          * INPUT or TEXTAREA element (as in the "I-beam" goes away but ths focus remains).
18154          * To cleanup after this, we need to capture the selection of any such element and
18155          * then restore it after we have restored the display style.
18156          *
18157          * @param target {Element} The top-most element being adjusted.
18158          * @private
18159          */
18160         getRightMarginFixCleaner: function (target) {
18161             var supports = Ext.supports,
18162                 hasInputBug = supports.DisplayChangeInputSelectionBug,
18163                 hasTextAreaBug = supports.DisplayChangeTextAreaSelectionBug;
18164
18165             if (hasInputBug || hasTextAreaBug) {
18166                 var activeEl = doc.activeElement || activeElement, // save a call
18167                     tag = activeEl && activeEl.tagName,
18168                     start,
18169                     end;
18170
18171                 if ((hasTextAreaBug && tag == 'TEXTAREA') ||
18172                     (hasInputBug && tag == 'INPUT' && activeEl.type == 'text')) {
18173                     if (ELEMENT.isAncestor(target, activeEl)) {
18174                         start = activeEl.selectionStart;
18175                         end = activeEl.selectionEnd;
18176
18177                         if (Ext.isNumber(start) && Ext.isNumber(end)) { // to be safe...
18178                             // We don't create the raw closure here inline because that
18179                             // will be costly even if we don't want to return it (nested
18180                             // function decls and exprs are often instantiated on entry
18181                             // regardless of whether execution ever reaches them):
18182                             return makeSelectionRestoreFn(activeEl, start, end);
18183                         }
18184                     }
18185                 }
18186             }
18187
18188             return Ext.emptyFn; // avoid special cases, just return a nop
18189         },
18190
18191         getViewWidth : function(full) {
18192             return full ? ELEMENT.getDocumentWidth() : ELEMENT.getViewportWidth();
18193         },
18194
18195         getViewHeight : function(full) {
18196             return full ? ELEMENT.getDocumentHeight() : ELEMENT.getViewportHeight();
18197         },
18198
18199         getDocumentHeight: function() {
18200             return Math.max(!isCSS1 ? doc.body.scrollHeight : doc.documentElement.scrollHeight, ELEMENT.getViewportHeight());
18201         },
18202
18203         getDocumentWidth: function() {
18204             return Math.max(!isCSS1 ? doc.body.scrollWidth : doc.documentElement.scrollWidth, ELEMENT.getViewportWidth());
18205         },
18206
18207         getViewportHeight: function(){
18208             return Ext.isIE ?
18209                    (Ext.isStrict ? doc.documentElement.clientHeight : doc.body.clientHeight) :
18210                    self.innerHeight;
18211         },
18212
18213         getViewportWidth : function() {
18214             return (!Ext.isStrict && !Ext.isOpera) ? doc.body.clientWidth :
18215                    Ext.isIE ? doc.documentElement.clientWidth : self.innerWidth;
18216         },
18217
18218         getY : function(el) {
18219             return ELEMENT.getXY(el)[1];
18220         },
18221
18222         getX : function(el) {
18223             return ELEMENT.getXY(el)[0];
18224         },
18225
18226         getXY : function(el) {
18227             var p,
18228                 pe,
18229                 b,
18230                 bt,
18231                 bl,
18232                 dbd,
18233                 x = 0,
18234                 y = 0,
18235                 scroll,
18236                 hasAbsolute,
18237                 bd = (doc.body || doc.documentElement),
18238                 ret = [0,0];
18239
18240             el = Ext.getDom(el);
18241
18242             if(el != bd){
18243                 hasAbsolute = fly(el).isStyle("position", "absolute");
18244
18245                 if (el.getBoundingClientRect) {
18246                     b = el.getBoundingClientRect();
18247                     scroll = fly(document).getScroll();
18248                     ret = [Math.round(b.left + scroll.left), Math.round(b.top + scroll.top)];
18249                 } else {
18250                     p = el;
18251
18252                     while (p) {
18253                         pe = fly(p);
18254                         x += p.offsetLeft;
18255                         y += p.offsetTop;
18256
18257                         hasAbsolute = hasAbsolute || pe.isStyle("position", "absolute");
18258
18259                         if (Ext.isGecko) {
18260                             y += bt = parseInt(pe.getStyle("borderTopWidth"), 10) || 0;
18261                             x += bl = parseInt(pe.getStyle("borderLeftWidth"), 10) || 0;
18262
18263                             if (p != el && !pe.isStyle('overflow','visible')) {
18264                                 x += bl;
18265                                 y += bt;
18266                             }
18267                         }
18268                         p = p.offsetParent;
18269                     }
18270
18271                     if (Ext.isSafari && hasAbsolute) {
18272                         x -= bd.offsetLeft;
18273                         y -= bd.offsetTop;
18274                     }
18275
18276                     if (Ext.isGecko && !hasAbsolute) {
18277                         dbd = fly(bd);
18278                         x += parseInt(dbd.getStyle("borderLeftWidth"), 10) || 0;
18279                         y += parseInt(dbd.getStyle("borderTopWidth"), 10) || 0;
18280                     }
18281
18282                     p = el.parentNode;
18283                     while (p && p != bd) {
18284                         if (!Ext.isOpera || (p.tagName != 'TR' && !fly(p).isStyle("display", "inline"))) {
18285                             x -= p.scrollLeft;
18286                             y -= p.scrollTop;
18287                         }
18288                         p = p.parentNode;
18289                     }
18290                     ret = [x,y];
18291                 }
18292             }
18293             return ret;
18294         },
18295
18296         setXY : function(el, xy) {
18297             (el = Ext.fly(el, '_setXY')).position();
18298
18299             var pts = el.translatePoints(xy),
18300                 style = el.dom.style,
18301                 pos;
18302
18303             for (pos in pts) {
18304                 if (!isNaN(pts[pos])) {
18305                     style[pos] = pts[pos] + "px";
18306                 }
18307             }
18308         },
18309
18310         setX : function(el, x) {
18311             ELEMENT.setXY(el, [x, false]);
18312         },
18313
18314         setY : function(el, y) {
18315             ELEMENT.setXY(el, [false, y]);
18316         },
18317
18318         /**
18319          * Serializes a DOM form into a url encoded string
18320          * @param {Object} form The form
18321          * @return {String} The url encoded form
18322          */
18323         serializeForm: function(form) {
18324             var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements,
18325                 hasSubmit = false,
18326                 encoder = encodeURIComponent,
18327                 name,
18328                 data = '',
18329                 type,
18330                 hasValue;
18331
18332             Ext.each(fElements, function(element){
18333                 name = element.name;
18334                 type = element.type;
18335
18336                 if (!element.disabled && name) {
18337                     if (/select-(one|multiple)/i.test(type)) {
18338                         Ext.each(element.options, function(opt){
18339                             if (opt.selected) {
18340                                 hasValue = opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttributeNode('value').specified;
18341                                 data += Ext.String.format("{0}={1}&", encoder(name), encoder(hasValue ? opt.value : opt.text));
18342                             }
18343                         });
18344                     } else if (!(/file|undefined|reset|button/i.test(type))) {
18345                         if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)) {
18346                             data += encoder(name) + '=' + encoder(element.value) + '&';
18347                             hasSubmit = /submit/i.test(type);
18348                         }
18349                     }
18350                 }
18351             });
18352             return data.substr(0, data.length - 1);
18353         }
18354     });
18355 })();
18356
18357 /**
18358  * @class Ext.core.Element
18359  */
18360
18361 Ext.core.Element.addMethods({
18362
18363     /**
18364      * Monitors this Element for the mouse leaving. Calls the function after the specified delay only if
18365      * the mouse was not moved back into the Element within the delay. If the mouse <i>was</i> moved
18366      * back in, the function is not called.
18367      * @param {Number} delay The delay <b>in milliseconds</b> to wait for possible mouse re-entry before calling the handler function.
18368      * @param {Function} handler The function to call if the mouse remains outside of this Element for the specified time.
18369      * @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to this Element.
18370      * @return {Object} The listeners object which was added to this element so that monitoring can be stopped. Example usage:</pre><code>
18371 // Hide the menu if the mouse moves out for 250ms or more
18372 this.mouseLeaveMonitor = this.menuEl.monitorMouseLeave(250, this.hideMenu, this);
18373
18374 ...
18375 // Remove mouseleave monitor on menu destroy
18376 this.menuEl.un(this.mouseLeaveMonitor);
18377 </code></pre>
18378      */
18379     monitorMouseLeave: function(delay, handler, scope) {
18380         var me = this,
18381             timer,
18382             listeners = {
18383                 mouseleave: function(e) {
18384                     timer = setTimeout(Ext.Function.bind(handler, scope||me, [e]), delay);
18385                 },
18386                 mouseenter: function() {
18387                     clearTimeout(timer);
18388                 },
18389                 freezeEvent: true
18390             };
18391
18392         me.on(listeners);
18393         return listeners;
18394     },
18395
18396     /**
18397      * Stops the specified event(s) from bubbling and optionally prevents the default action
18398      * @param {String/Array} eventName an event / array of events to stop from bubbling
18399      * @param {Boolean} preventDefault (optional) true to prevent the default action too
18400      * @return {Ext.core.Element} this
18401      */
18402     swallowEvent : function(eventName, preventDefault) {
18403         var me = this;
18404         function fn(e) {
18405             e.stopPropagation();
18406             if (preventDefault) {
18407                 e.preventDefault();
18408             }
18409         }
18410         
18411         if (Ext.isArray(eventName)) {
18412             Ext.each(eventName, function(e) {
18413                  me.on(e, fn);
18414             });
18415             return me;
18416         }
18417         me.on(eventName, fn);
18418         return me;
18419     },
18420
18421     /**
18422      * Create an event handler on this element such that when the event fires and is handled by this element,
18423      * it will be relayed to another object (i.e., fired again as if it originated from that object instead).
18424      * @param {String} eventName The type of event to relay
18425      * @param {Object} object Any object that extends {@link Ext.util.Observable} that will provide the context
18426      * for firing the relayed event
18427      */
18428     relayEvent : function(eventName, observable) {
18429         this.on(eventName, function(e) {
18430             observable.fireEvent(eventName, e);
18431         });
18432     },
18433
18434     /**
18435      * Removes Empty, or whitespace filled text nodes. Combines adjacent text nodes.
18436      * @param {Boolean} forceReclean (optional) By default the element
18437      * keeps track if it has been cleaned already so
18438      * you can call this over and over. However, if you update the element and
18439      * need to force a reclean, you can pass true.
18440      */
18441     clean : function(forceReclean) {
18442         var me  = this,
18443             dom = me.dom,
18444             n   = dom.firstChild,
18445             nx,
18446             ni  = -1;
18447
18448         if (Ext.core.Element.data(dom, 'isCleaned') && forceReclean !== true) {
18449             return me;
18450         }
18451
18452         while (n) {
18453             nx = n.nextSibling;
18454             if (n.nodeType == 3) {
18455                 // Remove empty/whitespace text nodes
18456                 if (!(/\S/.test(n.nodeValue))) {
18457                     dom.removeChild(n);
18458                 // Combine adjacent text nodes
18459                 } else if (nx && nx.nodeType == 3) {
18460                     n.appendData(Ext.String.trim(nx.data));
18461                     dom.removeChild(nx);
18462                     nx = n.nextSibling;
18463                     n.nodeIndex = ++ni;
18464                 }
18465             } else {
18466                 // Recursively clean
18467                 Ext.fly(n).clean();
18468                 n.nodeIndex = ++ni;
18469             }
18470             n = nx;
18471         }
18472
18473         Ext.core.Element.data(dom, 'isCleaned', true);
18474         return me;
18475     },
18476
18477     /**
18478      * Direct access to the Ext.ElementLoader {@link Ext.ElementLoader#load} method. The method takes the same object
18479      * parameter as {@link Ext.ElementLoader#load}
18480      * @return {Ext.core.Element} this
18481      */
18482     load : function(options) {
18483         this.getLoader().load(options);
18484         return this;
18485     },
18486
18487     /**
18488     * Gets this element's {@link Ext.ElementLoader ElementLoader}
18489     * @return {Ext.ElementLoader} The loader
18490     */
18491     getLoader : function() {
18492         var dom = this.dom,
18493             data = Ext.core.Element.data,
18494             loader = data(dom, 'loader');
18495             
18496         if (!loader) {
18497             loader = Ext.create('Ext.ElementLoader', {
18498                 target: this
18499             });
18500             data(dom, 'loader', loader);
18501         }
18502         return loader;
18503     },
18504
18505     /**
18506     * Update the innerHTML of this element, optionally searching for and processing scripts
18507     * @param {String} html The new HTML
18508     * @param {Boolean} loadScripts (optional) True to look for and process scripts (defaults to false)
18509     * @param {Function} callback (optional) For async script loading you can be notified when the update completes
18510     * @return {Ext.core.Element} this
18511      */
18512     update : function(html, loadScripts, callback) {
18513         var me = this,
18514             id,
18515             dom,
18516             interval;
18517             
18518         if (!me.dom) {
18519             return me;
18520         }
18521         html = html || '';
18522         dom = me.dom;
18523
18524         if (loadScripts !== true) {
18525             dom.innerHTML = html;
18526             Ext.callback(callback, me);
18527             return me;
18528         }
18529
18530         id  = Ext.id();
18531         html += '<span id="' + id + '"></span>';
18532
18533         interval = setInterval(function(){
18534             if (!document.getElementById(id)) {
18535                 return false;    
18536             }
18537             clearInterval(interval);
18538             var DOC    = document,
18539                 hd     = DOC.getElementsByTagName("head")[0],
18540                 re     = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
18541                 srcRe  = /\ssrc=([\'\"])(.*?)\1/i,
18542                 typeRe = /\stype=([\'\"])(.*?)\1/i,
18543                 match,
18544                 attrs,
18545                 srcMatch,
18546                 typeMatch,
18547                 el,
18548                 s;
18549
18550             while ((match = re.exec(html))) {
18551                 attrs = match[1];
18552                 srcMatch = attrs ? attrs.match(srcRe) : false;
18553                 if (srcMatch && srcMatch[2]) {
18554                    s = DOC.createElement("script");
18555                    s.src = srcMatch[2];
18556                    typeMatch = attrs.match(typeRe);
18557                    if (typeMatch && typeMatch[2]) {
18558                        s.type = typeMatch[2];
18559                    }
18560                    hd.appendChild(s);
18561                 } else if (match[2] && match[2].length > 0) {
18562                     if (window.execScript) {
18563                        window.execScript(match[2]);
18564                     } else {
18565                        window.eval(match[2]);
18566                     }
18567                 }
18568             }
18569             
18570             el = DOC.getElementById(id);
18571             if (el) {
18572                 Ext.removeNode(el);
18573             }
18574             Ext.callback(callback, me);
18575         }, 20);
18576         dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, '');
18577         return me;
18578     },
18579
18580     // inherit docs, overridden so we can add removeAnchor
18581     removeAllListeners : function() {
18582         this.removeAnchor();
18583         Ext.EventManager.removeAll(this.dom);
18584         return this;
18585     },
18586
18587     /**
18588      * Creates a proxy element of this element
18589      * @param {String/Object} config The class name of the proxy element or a DomHelper config object
18590      * @param {String/HTMLElement} renderTo (optional) The element or element id to render the proxy to (defaults to document.body)
18591      * @param {Boolean} matchBox (optional) True to align and size the proxy to this element now (defaults to false)
18592      * @return {Ext.core.Element} The new proxy element
18593      */
18594     createProxy : function(config, renderTo, matchBox) {
18595         config = (typeof config == 'object') ? config : {tag : "div", cls: config};
18596
18597         var me = this,
18598             proxy = renderTo ? Ext.core.DomHelper.append(renderTo, config, true) :
18599                                Ext.core.DomHelper.insertBefore(me.dom, config, true);
18600
18601         proxy.setVisibilityMode(Ext.core.Element.DISPLAY);
18602         proxy.hide();
18603         if (matchBox && me.setBox && me.getBox) { // check to make sure Element.position.js is loaded
18604            proxy.setBox(me.getBox());
18605         }
18606         return proxy;
18607     }
18608 });
18609 Ext.core.Element.prototype.clearListeners = Ext.core.Element.prototype.removeAllListeners;
18610
18611 /**
18612  * @class Ext.core.Element
18613  */
18614 Ext.core.Element.addMethods({
18615     /**
18616      * Gets the x,y coordinates specified by the anchor position on the element.
18617      * @param {String} anchor (optional) The specified anchor position (defaults to "c").  See {@link #alignTo}
18618      * for details on supported anchor positions.
18619      * @param {Boolean} local (optional) True to get the local (element top/left-relative) anchor position instead
18620      * of page coordinates
18621      * @param {Object} size (optional) An object containing the size to use for calculating anchor position
18622      * {width: (target width), height: (target height)} (defaults to the element's current size)
18623      * @return {Array} [x, y] An array containing the element's x and y coordinates
18624      */
18625     getAnchorXY : function(anchor, local, s){
18626         //Passing a different size is useful for pre-calculating anchors,
18627         //especially for anchored animations that change the el size.
18628         anchor = (anchor || "tl").toLowerCase();
18629         s = s || {};
18630
18631         var me = this,
18632             vp = me.dom == document.body || me.dom == document,
18633             w = s.width || vp ? Ext.core.Element.getViewWidth() : me.getWidth(),
18634             h = s.height || vp ? Ext.core.Element.getViewHeight() : me.getHeight(),
18635             xy,
18636             r = Math.round,
18637             o = me.getXY(),
18638             scroll = me.getScroll(),
18639             extraX = vp ? scroll.left : !local ? o[0] : 0,
18640             extraY = vp ? scroll.top : !local ? o[1] : 0,
18641             hash = {
18642                 c  : [r(w * 0.5), r(h * 0.5)],
18643                 t  : [r(w * 0.5), 0],
18644                 l  : [0, r(h * 0.5)],
18645                 r  : [w, r(h * 0.5)],
18646                 b  : [r(w * 0.5), h],
18647                 tl : [0, 0],
18648                 bl : [0, h],
18649                 br : [w, h],
18650                 tr : [w, 0]
18651             };
18652
18653         xy = hash[anchor];
18654         return [xy[0] + extraX, xy[1] + extraY];
18655     },
18656
18657     /**
18658      * Anchors an element to another element and realigns it when the window is resized.
18659      * @param {Mixed} element The element to align to.
18660      * @param {String} position The position to align to.
18661      * @param {Array} offsets (optional) Offset the positioning by [x, y]
18662      * @param {Boolean/Object} animate (optional) True for the default animation or a standard Element animation config object
18663      * @param {Boolean/Number} monitorScroll (optional) True to monitor body scroll and reposition. If this parameter
18664      * is a number, it is used as the buffer delay (defaults to 50ms).
18665      * @param {Function} callback The function to call after the animation finishes
18666      * @return {Ext.core.Element} this
18667      */
18668     anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
18669         var me = this,
18670             dom = me.dom,
18671             scroll = !Ext.isEmpty(monitorScroll),
18672             action = function(){
18673                 Ext.fly(dom).alignTo(el, alignment, offsets, animate);
18674                 Ext.callback(callback, Ext.fly(dom));
18675             },
18676             anchor = this.getAnchor();
18677
18678         // previous listener anchor, remove it
18679         this.removeAnchor();
18680         Ext.apply(anchor, {
18681             fn: action,
18682             scroll: scroll
18683         });
18684
18685         Ext.EventManager.onWindowResize(action, null);
18686
18687         if(scroll){
18688             Ext.EventManager.on(window, 'scroll', action, null,
18689                 {buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
18690         }
18691         action.call(me); // align immediately
18692         return me;
18693     },
18694
18695     /**
18696      * Remove any anchor to this element. See {@link #anchorTo}.
18697      * @return {Ext.core.Element} this
18698      */
18699     removeAnchor : function(){
18700         var me = this,
18701             anchor = this.getAnchor();
18702
18703         if(anchor && anchor.fn){
18704             Ext.EventManager.removeResizeListener(anchor.fn);
18705             if(anchor.scroll){
18706                 Ext.EventManager.un(window, 'scroll', anchor.fn);
18707             }
18708             delete anchor.fn;
18709         }
18710         return me;
18711     },
18712
18713     // private
18714     getAnchor : function(){
18715         var data = Ext.core.Element.data,
18716             dom = this.dom;
18717             if (!dom) {
18718                 return;
18719             }
18720             var anchor = data(dom, '_anchor');
18721
18722         if(!anchor){
18723             anchor = data(dom, '_anchor', {});
18724         }
18725         return anchor;
18726     },
18727
18728     getAlignVector: function(el, spec, offset) {
18729         var me = this,
18730             side = {t:"top", l:"left", r:"right", b: "bottom"},
18731             thisRegion = me.getRegion(),
18732             elRegion;
18733
18734         el = Ext.get(el);
18735         if(!el || !el.dom){
18736             Ext.Error.raise({
18737                 sourceClass: 'Ext.core.Element',
18738                 sourceMethod: 'getAlignVector',
18739                 msg: 'Attempted to align an element that doesn\'t exist'
18740             });
18741         }
18742
18743         elRegion = el.getRegion();
18744     },
18745
18746     /**
18747      * Gets the x,y coordinates to align this element with another element. See {@link #alignTo} for more info on the
18748      * supported position values.
18749      * @param {Mixed} element The element to align to.
18750      * @param {String} position (optional, defaults to "tl-bl?") The position to align to.
18751      * @param {Array} offsets (optional) Offset the positioning by [x, y]
18752      * @return {Array} [x, y]
18753      */
18754     getAlignToXY : function(el, p, o){
18755         el = Ext.get(el);
18756
18757         if(!el || !el.dom){
18758             Ext.Error.raise({
18759                 sourceClass: 'Ext.core.Element',
18760                 sourceMethod: 'getAlignToXY',
18761                 msg: 'Attempted to align an element that doesn\'t exist'
18762             });
18763         }
18764
18765         o = o || [0,0];
18766         p = (!p || p == "?" ? "tl-bl?" : (!(/-/).test(p) && p !== "" ? "tl-" + p : p || "tl-bl")).toLowerCase();
18767
18768         var me = this,
18769             d = me.dom,
18770             a1,
18771             a2,
18772             x,
18773             y,
18774             //constrain the aligned el to viewport if necessary
18775             w,
18776             h,
18777             r,
18778             dw = Ext.core.Element.getViewWidth() -10, // 10px of margin for ie
18779             dh = Ext.core.Element.getViewHeight()-10, // 10px of margin for ie
18780             p1y,
18781             p1x,
18782             p2y,
18783             p2x,
18784             swapY,
18785             swapX,
18786             doc = document,
18787             docElement = doc.documentElement,
18788             docBody = doc.body,
18789             scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0)+5,
18790             scrollY = (docElement.scrollTop || docBody.scrollTop || 0)+5,
18791             c = false, //constrain to viewport
18792             p1 = "",
18793             p2 = "",
18794             m = p.match(/^([a-z]+)-([a-z]+)(\?)?$/);
18795
18796         if(!m){
18797             Ext.Error.raise({
18798                 sourceClass: 'Ext.core.Element',
18799                 sourceMethod: 'getAlignToXY',
18800                 el: el,
18801                 position: p,
18802                 offset: o,
18803                 msg: 'Attemmpted to align an element with an invalid position: "' + p + '"'
18804             });
18805         }
18806
18807         p1 = m[1];
18808         p2 = m[2];
18809         c = !!m[3];
18810
18811         //Subtract the aligned el's internal xy from the target's offset xy
18812         //plus custom offset to get the aligned el's new offset xy
18813         a1 = me.getAnchorXY(p1, true);
18814         a2 = el.getAnchorXY(p2, false);
18815
18816         x = a2[0] - a1[0] + o[0];
18817         y = a2[1] - a1[1] + o[1];
18818
18819         if(c){
18820            w = me.getWidth();
18821            h = me.getHeight();
18822            r = el.getRegion();
18823            //If we are at a viewport boundary and the aligned el is anchored on a target border that is
18824            //perpendicular to the vp border, allow the aligned el to slide on that border,
18825            //otherwise swap the aligned el to the opposite border of the target.
18826            p1y = p1.charAt(0);
18827            p1x = p1.charAt(p1.length-1);
18828            p2y = p2.charAt(0);
18829            p2x = p2.charAt(p2.length-1);
18830            swapY = ((p1y=="t" && p2y=="b") || (p1y=="b" && p2y=="t"));
18831            swapX = ((p1x=="r" && p2x=="l") || (p1x=="l" && p2x=="r"));
18832
18833
18834            if (x + w > dw + scrollX) {
18835                 x = swapX ? r.left-w : dw+scrollX-w;
18836            }
18837            if (x < scrollX) {
18838                x = swapX ? r.right : scrollX;
18839            }
18840            if (y + h > dh + scrollY) {
18841                 y = swapY ? r.top-h : dh+scrollY-h;
18842             }
18843            if (y < scrollY){
18844                y = swapY ? r.bottom : scrollY;
18845            }
18846         }
18847         return [x,y];
18848     },
18849
18850     /**
18851      * Aligns this element with another element relative to the specified anchor points. If the other element is the
18852      * document it aligns it to the viewport.
18853      * The position parameter is optional, and can be specified in any one of the following formats:
18854      * <ul>
18855      *   <li><b>Blank</b>: Defaults to aligning the element's top-left corner to the target's bottom-left corner ("tl-bl").</li>
18856      *   <li><b>One anchor (deprecated)</b>: The passed anchor position is used as the target element's anchor point.
18857      *       The element being aligned will position its top-left corner (tl) to that point.  <i>This method has been
18858      *       deprecated in favor of the newer two anchor syntax below</i>.</li>
18859      *   <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
18860      *       element's anchor point, and the second value is used as the target's anchor point.</li>
18861      * </ul>
18862      * In addition to the anchor points, the position parameter also supports the "?" character.  If "?" is passed at the end of
18863      * the position string, the element will attempt to align as specified, but the position will be adjusted to constrain to
18864      * the viewport if necessary.  Note that the element being aligned might be swapped to align to a different position than
18865      * that specified in order to enforce the viewport constraints.
18866      * Following are all of the supported anchor positions:
18867 <pre>
18868 Value  Description
18869 -----  -----------------------------
18870 tl     The top left corner (default)
18871 t      The center of the top edge
18872 tr     The top right corner
18873 l      The center of the left edge
18874 c      In the center of the element
18875 r      The center of the right edge
18876 bl     The bottom left corner
18877 b      The center of the bottom edge
18878 br     The bottom right corner
18879 </pre>
18880 Example Usage:
18881 <pre><code>
18882 // align el to other-el using the default positioning ("tl-bl", non-constrained)
18883 el.alignTo("other-el");
18884
18885 // align the top left corner of el with the top right corner of other-el (constrained to viewport)
18886 el.alignTo("other-el", "tr?");
18887
18888 // align the bottom right corner of el with the center left edge of other-el
18889 el.alignTo("other-el", "br-l?");
18890
18891 // align the center of el with the bottom left corner of other-el and
18892 // adjust the x position by -6 pixels (and the y position by 0)
18893 el.alignTo("other-el", "c-bl", [-6, 0]);
18894 </code></pre>
18895      * @param {Mixed} element The element to align to.
18896      * @param {String} position (optional, defaults to "tl-bl?") The position to align to.
18897      * @param {Array} offsets (optional) Offset the positioning by [x, y]
18898      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
18899      * @return {Ext.core.Element} this
18900      */
18901     alignTo : function(element, position, offsets, animate){
18902         var me = this;
18903         return me.setXY(me.getAlignToXY(element, position, offsets),
18904                         me.anim && !!animate ? me.anim(animate) : false);
18905     },
18906
18907     // private ==>  used outside of core
18908     adjustForConstraints : function(xy, parent) {
18909         var vector = this.getConstrainVector(parent, xy);
18910         if (vector) {
18911             xy[0] += vector[0];
18912             xy[1] += vector[1];
18913         }
18914         return xy;
18915     },
18916
18917     /**
18918      * <p>Returns the <code>[X, Y]</code> vector by which this element must be translated to make a best attempt
18919      * to constrain within the passed constraint. Returns <code>false</code> is this element does not need to be moved.</p>
18920      * <p>Priority is given to constraining the top and left within the constraint.</p>
18921      * <p>The constraint may either be an existing element into which this element is to be constrained, or
18922      * an {@link Ext.util.Region Region} into which this element is to be constrained.</p>
18923      * @param constrainTo {Mixed} The Element or {@link Ext.util.Region Region} into which this element is to be constrained.
18924      * @param proposedPosition {Array} A proposed <code>[X, Y]</code> position to test for validity and to produce a vector for instead
18925      * of using this Element's current position;
18926      * @returns {Array} <b>If</b> this element <i>needs</i> to be translated, an <code>[X, Y]</code>
18927      * vector by which this element must be translated. Otherwise, <code>false</code>.
18928      */
18929     getConstrainVector: function(constrainTo, proposedPosition) {
18930         if (!(constrainTo instanceof Ext.util.Region)) {
18931             constrainTo = Ext.get(constrainTo).getViewRegion();
18932         }
18933         var thisRegion = this.getRegion(),
18934             vector = [0, 0],
18935             shadowSize = this.shadow && this.shadow.offset,
18936             overflowed = false;
18937
18938         // Shift this region to occupy the proposed position
18939         if (proposedPosition) {
18940             thisRegion.translateBy(proposedPosition[0] - thisRegion.x, proposedPosition[1] - thisRegion.y);
18941         }
18942
18943         // Reduce the constrain region to allow for shadow
18944         // TODO: Rewrite the Shadow class. When that's done, get the extra for each side from the Shadow.
18945         if (shadowSize) {
18946             constrainTo.adjust(0, -shadowSize, -shadowSize, shadowSize);
18947         }
18948
18949         // Constrain the X coordinate by however much this Element overflows
18950         if (thisRegion.right > constrainTo.right) {
18951             overflowed = true;
18952             vector[0] = (constrainTo.right - thisRegion.right);    // overflowed the right
18953         }
18954         if (thisRegion.left + vector[0] < constrainTo.left) {
18955             overflowed = true;
18956             vector[0] = (constrainTo.left - thisRegion.left);      // overflowed the left
18957         }
18958
18959         // Constrain the Y coordinate by however much this Element overflows
18960         if (thisRegion.bottom > constrainTo.bottom) {
18961             overflowed = true;
18962             vector[1] = (constrainTo.bottom - thisRegion.bottom);  // overflowed the bottom
18963         }
18964         if (thisRegion.top + vector[1] < constrainTo.top) {
18965             overflowed = true;
18966             vector[1] = (constrainTo.top - thisRegion.top);        // overflowed the top
18967         }
18968         return overflowed ? vector : false;
18969     },
18970
18971     /**
18972     * Calculates the x, y to center this element on the screen
18973     * @return {Array} The x, y values [x, y]
18974     */
18975     getCenterXY : function(){
18976         return this.getAlignToXY(document, 'c-c');
18977     },
18978
18979     /**
18980     * Centers the Element in either the viewport, or another Element.
18981     * @param {Mixed} centerIn (optional) The element in which to center the element.
18982     */
18983     center : function(centerIn){
18984         return this.alignTo(centerIn || document, 'c-c');
18985     }
18986 });
18987
18988 /**
18989  * @class Ext.core.Element
18990  */
18991 (function(){
18992
18993 var ELEMENT = Ext.core.Element,
18994     LEFT = "left",
18995     RIGHT = "right",
18996     TOP = "top",
18997     BOTTOM = "bottom",
18998     POSITION = "position",
18999     STATIC = "static",
19000     RELATIVE = "relative",
19001     AUTO = "auto",
19002     ZINDEX = "z-index";
19003
19004 Ext.override(Ext.core.Element, {
19005     /**
19006       * 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).
19007       * @return {Number} The X position of the element
19008       */
19009     getX : function(){
19010         return ELEMENT.getX(this.dom);
19011     },
19012
19013     /**
19014       * 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).
19015       * @return {Number} The Y position of the element
19016       */
19017     getY : function(){
19018         return ELEMENT.getY(this.dom);
19019     },
19020
19021     /**
19022       * 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).
19023       * @return {Array} The XY position of the element
19024       */
19025     getXY : function(){
19026         return ELEMENT.getXY(this.dom);
19027     },
19028
19029     /**
19030       * 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.
19031       * @param {Mixed} element The element to get the offsets from.
19032       * @return {Array} The XY page offsets (e.g. [100, -200])
19033       */
19034     getOffsetsTo : function(el){
19035         var o = this.getXY(),
19036             e = Ext.fly(el, '_internal').getXY();
19037         return [o[0]-e[0],o[1]-e[1]];
19038     },
19039
19040     /**
19041      * 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).
19042      * @param {Number} The X position of the element
19043      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19044      * @return {Ext.core.Element} this
19045      */
19046     setX : function(x, animate){
19047         return this.setXY([x, this.getY()], animate);
19048     },
19049
19050     /**
19051      * 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).
19052      * @param {Number} The Y position of the element
19053      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19054      * @return {Ext.core.Element} this
19055      */
19056     setY : function(y, animate){
19057         return this.setXY([this.getX(), y], animate);
19058     },
19059
19060     /**
19061      * Sets the element's left position directly using CSS style (instead of {@link #setX}).
19062      * @param {String} left The left CSS property value
19063      * @return {Ext.core.Element} this
19064      */
19065     setLeft : function(left){
19066         this.setStyle(LEFT, this.addUnits(left));
19067         return this;
19068     },
19069
19070     /**
19071      * Sets the element's top position directly using CSS style (instead of {@link #setY}).
19072      * @param {String} top The top CSS property value
19073      * @return {Ext.core.Element} this
19074      */
19075     setTop : function(top){
19076         this.setStyle(TOP, this.addUnits(top));
19077         return this;
19078     },
19079
19080     /**
19081      * Sets the element's CSS right style.
19082      * @param {String} right The right CSS property value
19083      * @return {Ext.core.Element} this
19084      */
19085     setRight : function(right){
19086         this.setStyle(RIGHT, this.addUnits(right));
19087         return this;
19088     },
19089
19090     /**
19091      * Sets the element's CSS bottom style.
19092      * @param {String} bottom The bottom CSS property value
19093      * @return {Ext.core.Element} this
19094      */
19095     setBottom : function(bottom){
19096         this.setStyle(BOTTOM, this.addUnits(bottom));
19097         return this;
19098     },
19099
19100     /**
19101      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
19102      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19103      * @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
19104      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19105      * @return {Ext.core.Element} this
19106      */
19107     setXY: function(pos, animate) {
19108         var me = this;
19109         if (!animate || !me.anim) {
19110             ELEMENT.setXY(me.dom, pos);
19111         }
19112         else {
19113             if (!Ext.isObject(animate)) {
19114                 animate = {};
19115             }
19116             me.animate(Ext.applyIf({ to: { x: pos[0], y: pos[1] } }, animate));
19117         }
19118         return me;
19119     },
19120
19121     /**
19122      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
19123      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19124      * @param {Number} x X value for new position (coordinates are page-based)
19125      * @param {Number} y Y value for new position (coordinates are page-based)
19126      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19127      * @return {Ext.core.Element} this
19128      */
19129     setLocation : function(x, y, animate){
19130         return this.setXY([x, y], animate);
19131     },
19132
19133     /**
19134      * Sets the position of the element in page coordinates, regardless of how the element is positioned.
19135      * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
19136      * @param {Number} x X value for new position (coordinates are page-based)
19137      * @param {Number} y Y value for new position (coordinates are page-based)
19138      * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
19139      * @return {Ext.core.Element} this
19140      */
19141     moveTo : function(x, y, animate){
19142         return this.setXY([x, y], animate);
19143     },
19144
19145     /**
19146      * Gets the left X coordinate
19147      * @param {Boolean} local True to get the local css position instead of page coordinate
19148      * @return {Number}
19149      */
19150     getLeft : function(local){
19151         return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
19152     },
19153
19154     /**
19155      * Gets the right X coordinate of the element (element X position + element width)
19156      * @param {Boolean} local True to get the local css position instead of page coordinate
19157      * @return {Number}
19158      */
19159     getRight : function(local){
19160         var me = this;
19161         return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
19162     },
19163
19164     /**
19165      * Gets the top Y coordinate
19166      * @param {Boolean} local True to get the local css position instead of page coordinate
19167      * @return {Number}
19168      */
19169     getTop : function(local) {
19170         return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
19171     },
19172
19173     /**
19174      * Gets the bottom Y coordinate of the element (element Y position + element height)
19175      * @param {Boolean} local True to get the local css position instead of page coordinate
19176      * @return {Number}
19177      */
19178     getBottom : function(local){
19179         var me = this;
19180         return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
19181     },
19182
19183     /**
19184     * Initializes positioning on this element. If a desired position is not passed, it will make the
19185     * the element positioned relative IF it is not already positioned.
19186     * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
19187     * @param {Number} zIndex (optional) The zIndex to apply
19188     * @param {Number} x (optional) Set the page X position
19189     * @param {Number} y (optional) Set the page Y position
19190     */
19191     position : function(pos, zIndex, x, y) {
19192         var me = this;
19193
19194         if (!pos && me.isStyle(POSITION, STATIC)){
19195             me.setStyle(POSITION, RELATIVE);
19196         } else if(pos) {
19197             me.setStyle(POSITION, pos);
19198         }
19199         if (zIndex){
19200             me.setStyle(ZINDEX, zIndex);
19201         }
19202         if (x || y) {
19203             me.setXY([x || false, y || false]);
19204         }
19205     },
19206
19207     /**
19208     * Clear positioning back to the default when the document was loaded
19209     * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
19210     * @return {Ext.core.Element} this
19211      */
19212     clearPositioning : function(value){
19213         value = value || '';
19214         this.setStyle({
19215             left : value,
19216             right : value,
19217             top : value,
19218             bottom : value,
19219             "z-index" : "",
19220             position : STATIC
19221         });
19222         return this;
19223     },
19224
19225     /**
19226     * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
19227     * snapshot before performing an update and then restoring the element.
19228     * @return {Object}
19229     */
19230     getPositioning : function(){
19231         var l = this.getStyle(LEFT);
19232         var t = this.getStyle(TOP);
19233         return {
19234             "position" : this.getStyle(POSITION),
19235             "left" : l,
19236             "right" : l ? "" : this.getStyle(RIGHT),
19237             "top" : t,
19238             "bottom" : t ? "" : this.getStyle(BOTTOM),
19239             "z-index" : this.getStyle(ZINDEX)
19240         };
19241     },
19242
19243     /**
19244     * Set positioning with an object returned by getPositioning().
19245     * @param {Object} posCfg
19246     * @return {Ext.core.Element} this
19247      */
19248     setPositioning : function(pc){
19249         var me = this,
19250             style = me.dom.style;
19251
19252         me.setStyle(pc);
19253
19254         if(pc.right == AUTO){
19255             style.right = "";
19256         }
19257         if(pc.bottom == AUTO){
19258             style.bottom = "";
19259         }
19260
19261         return me;
19262     },
19263
19264     /**
19265      * Translates the passed page coordinates into left/top css values for this element
19266      * @param {Number/Array} x The page x or an array containing [x, y]
19267      * @param {Number} y (optional) The page y, required if x is not an array
19268      * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
19269      */
19270     translatePoints: function(x, y) {
19271         if (Ext.isArray(x)) {
19272              y = x[1];
19273              x = x[0];
19274         }
19275         var me = this,
19276             relative = me.isStyle(POSITION, RELATIVE),
19277             o = me.getXY(),
19278             left = parseInt(me.getStyle(LEFT), 10),
19279             top = parseInt(me.getStyle(TOP), 10);
19280
19281         if (!Ext.isNumber(left)) {
19282             left = relative ? 0 : me.dom.offsetLeft;
19283         }
19284         if (!Ext.isNumber(top)) {
19285             top = relative ? 0 : me.dom.offsetTop;
19286         }
19287         left = (Ext.isNumber(x)) ? x - o[0] + left : undefined;
19288         top = (Ext.isNumber(y)) ? y - o[1] + top : undefined;
19289         return {
19290             left: left,
19291             top: top
19292         };
19293     },
19294
19295     /**
19296      * 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.
19297      * @param {Object} box The box to fill {x, y, width, height}
19298      * @param {Boolean} adjust (optional) Whether to adjust for box-model issues automatically
19299      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
19300      * @return {Ext.core.Element} this
19301      */
19302     setBox: function(box, adjust, animate) {
19303         var me = this,
19304             w = box.width,
19305             h = box.height;
19306         if ((adjust && !me.autoBoxAdjust) && !me.isBorderBox()) {
19307             w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
19308             h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
19309         }
19310         me.setBounds(box.x, box.y, w, h, animate);
19311         return me;
19312     },
19313
19314     /**
19315      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
19316      * set another Element's size/location to match this element.
19317      * @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
19318      * @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
19319      * @return {Object} box An object in the format<pre><code>
19320 {
19321     x: &lt;Element's X position>,
19322     y: &lt;Element's Y position>,
19323     width: &lt;Element's width>,
19324     height: &lt;Element's height>,
19325     bottom: &lt;Element's lower bound>,
19326     right: &lt;Element's rightmost bound>
19327 }
19328 </code></pre>
19329      * The returned object may also be addressed as an Array where index 0 contains the X position
19330      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
19331      */
19332     getBox: function(contentBox, local) {
19333         var me = this,
19334             xy,
19335             left,
19336             top,
19337             getBorderWidth = me.getBorderWidth,
19338             getPadding = me.getPadding,
19339             l, r, t, b, w, h, bx;
19340         if (!local) {
19341             xy = me.getXY();
19342         } else {
19343             left = parseInt(me.getStyle("left"), 10) || 0;
19344             top = parseInt(me.getStyle("top"), 10) || 0;
19345             xy = [left, top];
19346         }
19347         w = me.getWidth();
19348         h = me.getHeight();
19349         if (!contentBox) {
19350             bx = {
19351                 x: xy[0],
19352                 y: xy[1],
19353                 0: xy[0],
19354                 1: xy[1],
19355                 width: w,
19356                 height: h
19357             };
19358         } else {
19359             l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
19360             r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
19361             t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
19362             b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
19363             bx = {
19364                 x: xy[0] + l,
19365                 y: xy[1] + t,
19366                 0: xy[0] + l,
19367                 1: xy[1] + t,
19368                 width: w - (l + r),
19369                 height: h - (t + b)
19370             };
19371         }
19372         bx.right = bx.x + bx.width;
19373         bx.bottom = bx.y + bx.height;
19374         return bx;
19375     },
19376
19377     /**
19378      * Move this element relative to its current position.
19379      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
19380      * @param {Number} distance How far to move the element in pixels
19381      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
19382      * @return {Ext.core.Element} this
19383      */
19384     move: function(direction, distance, animate) {
19385         var me = this,
19386             xy = me.getXY(),
19387             x = xy[0],
19388             y = xy[1],
19389             left = [x - distance, y],
19390             right = [x + distance, y],
19391             top = [x, y - distance],
19392             bottom = [x, y + distance],
19393             hash = {
19394                 l: left,
19395                 left: left,
19396                 r: right,
19397                 right: right,
19398                 t: top,
19399                 top: top,
19400                 up: top,
19401                 b: bottom,
19402                 bottom: bottom,
19403                 down: bottom
19404             };
19405
19406         direction = direction.toLowerCase();
19407         me.moveTo(hash[direction][0], hash[direction][1], animate);
19408     },
19409
19410     /**
19411      * Quick set left and top adding default units
19412      * @param {String} left The left CSS property value
19413      * @param {String} top The top CSS property value
19414      * @return {Ext.core.Element} this
19415      */
19416     setLeftTop: function(left, top) {
19417         var me = this,
19418             style = me.dom.style;
19419         style.left = me.addUnits(left);
19420         style.top = me.addUnits(top);
19421         return me;
19422     },
19423
19424     /**
19425      * Returns the region of this element.
19426      * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
19427      * @return {Region} A Ext.util.Region containing "top, left, bottom, right" member data.
19428      */
19429     getRegion: function() {
19430         return this.getPageBox(true);
19431     },
19432
19433     /**
19434      * Returns the <b>content</b> region of this element. That is the region within the borders and padding.
19435      * @return {Region} A Ext.util.Region containing "top, left, bottom, right" member data.
19436      */
19437     getViewRegion: function() {
19438         var me = this,
19439             isBody = me.dom === document.body,
19440             scroll, pos, top, left, width, height;
19441             
19442         // For the body we want to do some special logic
19443         if (isBody) {
19444             scroll = me.getScroll();
19445             left = scroll.left;
19446             top = scroll.top;
19447             width = Ext.core.Element.getViewportWidth();
19448             height = Ext.core.Element.getViewportHeight();
19449         }
19450         else {
19451             pos = me.getXY();
19452             left = pos[0] + me.getBorderWidth('l') + me.getPadding('l');
19453             top = pos[1] + me.getBorderWidth('t') + me.getPadding('t');
19454             width = me.getWidth(true);
19455             height = me.getHeight(true);
19456         }
19457
19458         return Ext.create('Ext.util.Region', top, left + width, top + height, left);
19459     },
19460
19461     /**
19462      * Return an object defining the area of this Element which can be passed to {@link #setBox} to
19463      * set another Element's size/location to match this element.
19464      * @param {Boolean} asRegion(optional) If true an Ext.util.Region will be returned
19465      * @return {Object} box An object in the format<pre><code>
19466 {
19467     x: &lt;Element's X position>,
19468     y: &lt;Element's Y position>,
19469     width: &lt;Element's width>,
19470     height: &lt;Element's height>,
19471     bottom: &lt;Element's lower bound>,
19472     right: &lt;Element's rightmost bound>
19473 }
19474 </code></pre>
19475      * The returned object may also be addressed as an Array where index 0 contains the X position
19476      * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
19477      */
19478     getPageBox : function(getRegion) {
19479         var me = this,
19480             el = me.dom,
19481             isDoc = el === document.body,
19482             w = isDoc ? Ext.core.Element.getViewWidth()  : el.offsetWidth,
19483             h = isDoc ? Ext.core.Element.getViewHeight() : el.offsetHeight,
19484             xy = me.getXY(),
19485             t = xy[1],
19486             r = xy[0] + w,
19487             b = xy[1] + h,
19488             l = xy[0];
19489
19490         if (getRegion) {
19491             return Ext.create('Ext.util.Region', t, r, b, l);
19492         }
19493         else {
19494             return {
19495                 left: l,
19496                 top: t,
19497                 width: w,
19498                 height: h,
19499                 right: r,
19500                 bottom: b
19501             };
19502         }
19503     },
19504
19505     /**
19506      * Sets the element's position and size in one shot. If animation is true then width, height, x and y will be animated concurrently.
19507      * @param {Number} x X value for new position (coordinates are page-based)
19508      * @param {Number} y Y value for new position (coordinates are page-based)
19509      * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
19510      * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
19511      * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
19512      * </ul></div>
19513      * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
19514      * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
19515      * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
19516      * </ul></div>
19517      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
19518      * @return {Ext.core.Element} this
19519      */
19520     setBounds: function(x, y, width, height, animate) {
19521         var me = this;
19522         if (!animate || !me.anim) {
19523             me.setSize(width, height);
19524             me.setLocation(x, y);
19525         } else {
19526             if (!Ext.isObject(animate)) {
19527                 animate = {};
19528             }
19529             me.animate(Ext.applyIf({
19530                 to: {
19531                     x: x,
19532                     y: y,
19533                     width: me.adjustWidth(width),
19534                     height: me.adjustHeight(height)
19535                 }
19536             }, animate));
19537         }
19538         return me;
19539     },
19540
19541     /**
19542      * Sets the element's position and size the specified region. If animation is true then width, height, x and y will be animated concurrently.
19543      * @param {Ext.util.Region} region The region to fill
19544      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
19545      * @return {Ext.core.Element} this
19546      */
19547     setRegion: function(region, animate) {
19548         return this.setBounds(region.left, region.top, region.right - region.left, region.bottom - region.top, animate);
19549     }
19550 });
19551 })();
19552
19553 /**
19554  * @class Ext.core.Element
19555  */
19556 Ext.override(Ext.core.Element, {
19557     /**
19558      * Returns true if this element is scrollable.
19559      * @return {Boolean}
19560      */
19561     isScrollable : function(){
19562         var dom = this.dom;
19563         return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
19564     },
19565
19566     /**
19567      * Returns the current scroll position of the element.
19568      * @return {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
19569      */
19570     getScroll : function() {
19571         var d = this.dom, 
19572             doc = document,
19573             body = doc.body,
19574             docElement = doc.documentElement,
19575             l,
19576             t,
19577             ret;
19578
19579         if (d == doc || d == body) {
19580             if (Ext.isIE && Ext.isStrict) {
19581                 l = docElement.scrollLeft; 
19582                 t = docElement.scrollTop;
19583             } else {
19584                 l = window.pageXOffset;
19585                 t = window.pageYOffset;
19586             }
19587             ret = {
19588                 left: l || (body ? body.scrollLeft : 0), 
19589                 top : t || (body ? body.scrollTop : 0)
19590             };
19591         } else {
19592             ret = {
19593                 left: d.scrollLeft, 
19594                 top : d.scrollTop
19595             };
19596         }
19597         
19598         return ret;
19599     },
19600     
19601     /**
19602      * 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().
19603      * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
19604      * @param {Number} value The new scroll value
19605      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
19606      * @return {Element} this
19607      */
19608     scrollTo : function(side, value, animate) {
19609         //check if we're scrolling top or left
19610         var top = /top/i.test(side),
19611             me = this,
19612             dom = me.dom,
19613             obj = {},
19614             prop;
19615         if (!animate || !me.anim) {
19616             // just setting the value, so grab the direction
19617             prop = 'scroll' + (top ? 'Top' : 'Left');
19618             dom[prop] = value;
19619         }
19620         else {
19621             if (!Ext.isObject(animate)) {
19622                 animate = {};
19623             }
19624             obj['scroll' + (top ? 'Top' : 'Left')] = value;
19625             me.animate(Ext.applyIf({
19626                 to: obj
19627             }, animate));
19628         }
19629         return me;
19630     },
19631
19632     /**
19633      * Scrolls this element into view within the passed container.
19634      * @param {Mixed} container (optional) The container element to scroll (defaults to document.body).  Should be a
19635      * string (id), dom node, or Ext.core.Element.
19636      * @param {Boolean} hscroll (optional) False to disable horizontal scroll (defaults to true)
19637      * @return {Ext.core.Element} this
19638      */
19639     scrollIntoView : function(container, hscroll) {
19640         container = Ext.getDom(container) || Ext.getBody().dom;
19641         var el = this.dom,
19642             offsets = this.getOffsetsTo(container),
19643             // el's box
19644             left = offsets[0] + container.scrollLeft,
19645             top = offsets[1] + container.scrollTop,
19646             bottom = top + el.offsetHeight,
19647             right = left + el.offsetWidth,
19648             // ct's box
19649             ctClientHeight = container.clientHeight,
19650             ctScrollTop = parseInt(container.scrollTop, 10),
19651             ctScrollLeft = parseInt(container.scrollLeft, 10),
19652             ctBottom = ctScrollTop + ctClientHeight,
19653             ctRight = ctScrollLeft + container.clientWidth;
19654
19655         if (el.offsetHeight > ctClientHeight || top < ctScrollTop) {
19656             container.scrollTop = top;
19657         } else if (bottom > ctBottom) {
19658             container.scrollTop = bottom - ctClientHeight;
19659         }
19660         // corrects IE, other browsers will ignore
19661         container.scrollTop = container.scrollTop;
19662
19663         if (hscroll !== false) {
19664             if (el.offsetWidth > container.clientWidth || left < ctScrollLeft) {
19665                 container.scrollLeft = left;
19666             }
19667             else if (right > ctRight) {
19668                 container.scrollLeft = right - container.clientWidth;
19669             }
19670             container.scrollLeft = container.scrollLeft;
19671         }
19672         return this;
19673     },
19674
19675     // private
19676     scrollChildIntoView : function(child, hscroll) {
19677         Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
19678     },
19679
19680     /**
19681      * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
19682      * within this element's scrollable range.
19683      * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
19684      * @param {Number} distance How far to scroll the element in pixels
19685      * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
19686      * @return {Boolean} Returns true if a scroll was triggered or false if the element
19687      * was scrolled as far as it could go.
19688      */
19689      scroll : function(direction, distance, animate) {
19690         if (!this.isScrollable()) {
19691             return false;
19692         }
19693         var el = this.dom,
19694             l = el.scrollLeft, t = el.scrollTop,
19695             w = el.scrollWidth, h = el.scrollHeight,
19696             cw = el.clientWidth, ch = el.clientHeight,
19697             scrolled = false, v,
19698             hash = {
19699                 l: Math.min(l + distance, w-cw),
19700                 r: v = Math.max(l - distance, 0),
19701                 t: Math.max(t - distance, 0),
19702                 b: Math.min(t + distance, h-ch)
19703             };
19704             hash.d = hash.b;
19705             hash.u = hash.t;
19706
19707         direction = direction.substr(0, 1);
19708         if ((v = hash[direction]) > -1) {
19709             scrolled = true;
19710             this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.anim(animate));
19711         }
19712         return scrolled;
19713     }
19714 });
19715 /**
19716  * @class Ext.core.Element
19717  */
19718 Ext.core.Element.addMethods(
19719     function() {
19720         var VISIBILITY      = "visibility",
19721             DISPLAY         = "display",
19722             HIDDEN          = "hidden",
19723             NONE            = "none",
19724             XMASKED         = Ext.baseCSSPrefix + "masked",
19725             XMASKEDRELATIVE = Ext.baseCSSPrefix + "masked-relative",
19726             data            = Ext.core.Element.data;
19727
19728         return {
19729             /**
19730              * Checks whether the element is currently visible using both visibility and display properties.
19731              * @param {Boolean} deep (optional) True to walk the dom and see if parent elements are hidden (defaults to false)
19732              * @return {Boolean} True if the element is currently visible, else false
19733              */
19734             isVisible : function(deep) {
19735                 var vis = !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE),
19736                     p   = this.dom.parentNode;
19737
19738                 if (deep !== true || !vis) {
19739                     return vis;
19740                 }
19741
19742                 while (p && !(/^body/i.test(p.tagName))) {
19743                     if (!Ext.fly(p, '_isVisible').isVisible()) {
19744                         return false;
19745                     }
19746                     p = p.parentNode;
19747                 }
19748                 return true;
19749             },
19750
19751             /**
19752              * Returns true if display is not "none"
19753              * @return {Boolean}
19754              */
19755             isDisplayed : function() {
19756                 return !this.isStyle(DISPLAY, NONE);
19757             },
19758
19759             /**
19760              * Convenience method for setVisibilityMode(Element.DISPLAY)
19761              * @param {String} display (optional) What to set display to when visible
19762              * @return {Ext.core.Element} this
19763              */
19764             enableDisplayMode : function(display) {
19765                 this.setVisibilityMode(Ext.core.Element.DISPLAY);
19766
19767                 if (!Ext.isEmpty(display)) {
19768                     data(this.dom, 'originalDisplay', display);
19769                 }
19770
19771                 return this;
19772             },
19773
19774             /**
19775              * Puts a mask over this element to disable user interaction. Requires core.css.
19776              * This method can only be applied to elements which accept child nodes.
19777              * @param {String} msg (optional) A message to display in the mask
19778              * @param {String} msgCls (optional) A css class to apply to the msg element
19779              * @return {Element} The mask element
19780              */
19781             mask : function(msg, msgCls) {
19782                 var me  = this,
19783                     dom = me.dom,
19784                     setExpression = dom.style.setExpression,
19785                     dh  = Ext.core.DomHelper,
19786                     EXTELMASKMSG = Ext.baseCSSPrefix + "mask-msg",
19787                     el,
19788                     mask;
19789
19790                 if (!(/^body/i.test(dom.tagName) && me.getStyle('position') == 'static')) {
19791                     me.addCls(XMASKEDRELATIVE);
19792                 }
19793                 el = data(dom, 'maskMsg');
19794                 if (el) {
19795                     el.remove();
19796                 }
19797                 el = data(dom, 'mask');
19798                 if (el) {
19799                     el.remove();
19800                 }
19801
19802                 mask = dh.append(dom, {cls : Ext.baseCSSPrefix + "mask"}, true);
19803                 data(dom, 'mask', mask);
19804
19805                 me.addCls(XMASKED);
19806                 mask.setDisplayed(true);
19807
19808                 if (typeof msg == 'string') {
19809                     var mm = dh.append(dom, {cls : EXTELMASKMSG, cn:{tag:'div'}}, true);
19810                     data(dom, 'maskMsg', mm);
19811                     mm.dom.className = msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG;
19812                     mm.dom.firstChild.innerHTML = msg;
19813                     mm.setDisplayed(true);
19814                     mm.center(me);
19815                 }
19816                 // NOTE: CSS expressions are resource intensive and to be used only as a last resort
19817                 // These expressions are removed as soon as they are no longer necessary - in the unmask method.
19818                 // In normal use cases an element will be masked for a limited period of time.
19819                 // Fix for https://sencha.jira.com/browse/EXTJSIV-19.
19820                 // IE6 strict mode and IE6-9 quirks mode takes off left+right padding when calculating width!
19821                 if (!Ext.supports.IncludePaddingInWidthCalculation && setExpression) {
19822                     mask.dom.style.setExpression('width', 'this.parentNode.offsetWidth + "px"');
19823                 }
19824
19825                 // Some versions and modes of IE subtract top+bottom padding when calculating height.
19826                 // Different versions from those which make the same error for width!
19827                 if (!Ext.supports.IncludePaddingInHeightCalculation && setExpression) {
19828                     mask.dom.style.setExpression('height', 'this.parentNode.offsetHeight + "px"');
19829                 }
19830                 // ie will not expand full height automatically
19831                 else if (Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto') {
19832                     mask.setSize(undefined, me.getHeight());
19833                 }
19834                 return mask;
19835             },
19836
19837             /**
19838              * Removes a previously applied mask.
19839              */
19840             unmask : function() {
19841                 var me      = this,
19842                     dom     = me.dom,
19843                     mask    = data(dom, 'mask'),
19844                     maskMsg = data(dom, 'maskMsg');
19845
19846                 if (mask) {
19847                     // Remove resource-intensive CSS expressions as soon as they are not required.
19848                     if (mask.dom.style.clearExpression) {
19849                         mask.dom.style.clearExpression('width');
19850                         mask.dom.style.clearExpression('height');
19851                     }
19852                     if (maskMsg) {
19853                         maskMsg.remove();
19854                         data(dom, 'maskMsg', undefined);
19855                     }
19856
19857                     mask.remove();
19858                     data(dom, 'mask', undefined);
19859                     me.removeCls([XMASKED, XMASKEDRELATIVE]);
19860                 }
19861             },
19862             /**
19863              * Returns true if this element is masked. Also re-centers any displayed message within the mask.
19864              * @return {Boolean}
19865              */
19866             isMasked : function() {
19867                 var me = this,
19868                     mask = data(me.dom, 'mask'),
19869                     maskMsg = data(me.dom, 'maskMsg');
19870
19871                 if (mask && mask.isVisible()) {
19872                     if (maskMsg) {
19873                         maskMsg.center(me);
19874                     }
19875                     return true;
19876                 }
19877                 return false;
19878             },
19879
19880             /**
19881              * Creates an iframe shim for this element to keep selects and other windowed objects from
19882              * showing through.
19883              * @return {Ext.core.Element} The new shim element
19884              */
19885             createShim : function() {
19886                 var el = document.createElement('iframe'),
19887                     shim;
19888
19889                 el.frameBorder = '0';
19890                 el.className = Ext.baseCSSPrefix + 'shim';
19891                 el.src = Ext.SSL_SECURE_URL;
19892                 shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
19893                 shim.autoBoxAdjust = false;
19894                 return shim;
19895             }
19896         };
19897     }()
19898 );
19899 /**
19900  * @class Ext.core.Element
19901  */
19902 Ext.core.Element.addMethods({
19903     /**
19904      * Convenience method for constructing a KeyMap
19905      * @param {Number/Array/Object/String} 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:
19906      * <code>{key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}</code>
19907      * @param {Function} fn The function to call
19908      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the specified function is executed. Defaults to this Element.
19909      * @return {Ext.util.KeyMap} The KeyMap created
19910      */
19911     addKeyListener : function(key, fn, scope){
19912         var config;
19913         if(typeof key != 'object' || Ext.isArray(key)){
19914             config = {
19915                 key: key,
19916                 fn: fn,
19917                 scope: scope
19918             };
19919         }else{
19920             config = {
19921                 key : key.key,
19922                 shift : key.shift,
19923                 ctrl : key.ctrl,
19924                 alt : key.alt,
19925                 fn: fn,
19926                 scope: scope
19927             };
19928         }
19929         return Ext.create('Ext.util.KeyMap', this, config);
19930     },
19931
19932     /**
19933      * Creates a KeyMap for this element
19934      * @param {Object} config The KeyMap config. See {@link Ext.util.KeyMap} for more details
19935      * @return {Ext.util.KeyMap} The KeyMap created
19936      */
19937     addKeyMap : function(config){
19938         return Ext.create('Ext.util.KeyMap', this, config);
19939     }
19940 });
19941
19942 //Import the newly-added Ext.core.Element functions into CompositeElementLite. We call this here because
19943 //Element.keys.js is the last extra Ext.core.Element include in the ext-all.js build
19944 Ext.CompositeElementLite.importElementMethods();
19945
19946 /**
19947  * @class Ext.CompositeElementLite
19948  */
19949 Ext.apply(Ext.CompositeElementLite.prototype, {
19950     addElements : function(els, root){
19951         if(!els){
19952             return this;
19953         }
19954         if(typeof els == "string"){
19955             els = Ext.core.Element.selectorFunction(els, root);
19956         }
19957         var yels = this.elements;
19958         Ext.each(els, function(e) {
19959             yels.push(Ext.get(e));
19960         });
19961         return this;
19962     },
19963
19964     /**
19965      * Returns the first Element
19966      * @return {Ext.core.Element}
19967      */
19968     first : function(){
19969         return this.item(0);
19970     },
19971
19972     /**
19973      * Returns the last Element
19974      * @return {Ext.core.Element}
19975      */
19976     last : function(){
19977         return this.item(this.getCount()-1);
19978     },
19979
19980     /**
19981      * Returns true if this composite contains the passed element
19982      * @param el {Mixed} The id of an element, or an Ext.core.Element, or an HtmlElement to find within the composite collection.
19983      * @return Boolean
19984      */
19985     contains : function(el){
19986         return this.indexOf(el) != -1;
19987     },
19988
19989     /**
19990     * Removes the specified element(s).
19991     * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
19992     * or an array of any of those.
19993     * @param {Boolean} removeDom (optional) True to also remove the element from the document
19994     * @return {CompositeElement} this
19995     */
19996     removeElement : function(keys, removeDom){
19997         var me = this,
19998             els = this.elements,
19999             el;
20000         Ext.each(keys, function(val){
20001             if ((el = (els[val] || els[val = me.indexOf(val)]))) {
20002                 if(removeDom){
20003                     if(el.dom){
20004                         el.remove();
20005                     }else{
20006                         Ext.removeNode(el);
20007                     }
20008                 }
20009                 els.splice(val, 1);
20010             }
20011         });
20012         return this;
20013     }
20014 });
20015
20016 /**
20017  * @class Ext.CompositeElement
20018  * @extends Ext.CompositeElementLite
20019  * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
20020  * members, or to perform collective actions upon the whole set.</p>
20021  * <p>Although they are not listed, this class supports all of the methods of {@link Ext.core.Element} and
20022  * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
20023  * <p>All methods return <i>this</i> and can be chained.</p>
20024  * Usage:
20025 <pre><code>
20026 var els = Ext.select("#some-el div.some-class", true);
20027 // or select directly from an existing element
20028 var el = Ext.get('some-el');
20029 el.select('div.some-class', true);
20030
20031 els.setWidth(100); // all elements become 100 width
20032 els.hide(true); // all elements fade out and hide
20033 // or
20034 els.setWidth(100).hide(true);
20035 </code></pre>
20036  */
20037 Ext.CompositeElement = Ext.extend(Ext.CompositeElementLite, {
20038     
20039     constructor : function(els, root){
20040         this.elements = [];
20041         this.add(els, root);
20042     },
20043     
20044     // private
20045     getElement : function(el){
20046         // In this case just return it, since we already have a reference to it
20047         return el;
20048     },
20049     
20050     // private
20051     transformElement : function(el){
20052         return Ext.get(el);
20053     }
20054
20055     /**
20056     * Adds elements to this composite.
20057     * @param {String/Array} els A string CSS selector, an array of elements or an element
20058     * @return {CompositeElement} this
20059     */
20060
20061     /**
20062      * Returns the Element object at the specified index
20063      * @param {Number} index
20064      * @return {Ext.core.Element}
20065      */
20066
20067     /**
20068      * Iterates each `element` in this `composite` calling the supplied function using {@link Ext#each Ext.each}.
20069      * @param {Function} fn 
20070
20071 The function to be called with each
20072 `element`. If the supplied function returns <tt>false</tt>,
20073 iteration stops. This function is called with the following arguments:
20074
20075 - `element` : __Ext.core.Element++
20076     The element at the current `index` in the `composite`
20077     
20078 - `composite` : __Object__ 
20079     This composite.
20080
20081 - `index` : __Number__ 
20082     The current index within the `composite`
20083
20084      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the specified function is executed.
20085      * Defaults to the <code>element</code> at the current <code>index</code>
20086      * within the composite.
20087      * @return {CompositeElement} this
20088      * @markdown
20089      */
20090 });
20091
20092 /**
20093  * Selects elements based on the passed CSS selector to enable {@link Ext.core.Element Element} methods
20094  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
20095  * {@link Ext.CompositeElementLite CompositeElementLite} object.
20096  * @param {String/Array} selector The CSS selector or an array of elements
20097  * @param {Boolean} unique (optional) true to create a unique Ext.core.Element for each element (defaults to a shared flyweight object)
20098  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
20099  * @return {CompositeElementLite/CompositeElement}
20100  * @member Ext.core.Element
20101  * @method select
20102  */
20103 Ext.core.Element.select = function(selector, unique, root){
20104     var els;
20105     if(typeof selector == "string"){
20106         els = Ext.core.Element.selectorFunction(selector, root);
20107     }else if(selector.length !== undefined){
20108         els = selector;
20109     }else{
20110         Ext.Error.raise({
20111             sourceClass: "Ext.core.Element",
20112             sourceMethod: "select",
20113             selector: selector,
20114             unique: unique,
20115             root: root,
20116             msg: "Invalid selector specified: " + selector
20117         });
20118     }
20119     return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
20120 };
20121
20122 /**
20123  * Selects elements based on the passed CSS selector to enable {@link Ext.core.Element Element} methods
20124  * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
20125  * {@link Ext.CompositeElementLite CompositeElementLite} object.
20126  * @param {String/Array} selector The CSS selector or an array of elements
20127  * @param {Boolean} unique (optional) true to create a unique Ext.core.Element for each element (defaults to a shared flyweight object)
20128  * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
20129  * @return {CompositeElementLite/CompositeElement}
20130  * @member Ext
20131  * @method select
20132  */
20133 Ext.select = Ext.core.Element.select;
20134
20135
20136 /*
20137 Ext JS - JavaScript Library
20138 Copyright (c) 2006-2011, Sencha Inc.
20139 All rights reserved.
20140 licensing@sencha.com
20141 */
20142 /**
20143  * @class Ext.util.Observable
20144  * Base class that provides a common interface for publishing events. Subclasses are expected to
20145  * to have a property "events" with all the events defined, and, optionally, a property "listeners"
20146  * with configured listeners defined.<br>
20147  * For example:
20148  * <pre><code>
20149 Ext.define('Employee', {
20150     extend: 'Ext.util.Observable',
20151     constructor: function(config){
20152         this.name = config.name;
20153         this.addEvents({
20154             "fired" : true,
20155             "quit" : true
20156         });
20157
20158         // Copy configured listeners into *this* object so that the base class&#39;s
20159         // constructor will add them.
20160         this.listeners = config.listeners;
20161
20162         // Call our superclass constructor to complete construction process.
20163         Employee.superclass.constructor.call(this, config)
20164     }
20165 });
20166 </code></pre>
20167  * This could then be used like this:<pre><code>
20168 var newEmployee = new Employee({
20169     name: employeeName,
20170     listeners: {
20171         quit: function() {
20172             // By default, "this" will be the object that fired the event.
20173             alert(this.name + " has quit!");
20174         }
20175     }
20176 });
20177 </code></pre>
20178  */
20179
20180 Ext.define('Ext.util.Observable', {
20181
20182     /* Begin Definitions */
20183
20184     requires: ['Ext.util.Event'],
20185
20186     statics: {
20187         /**
20188          * Removes <b>all</b> added captures from the Observable.
20189          * @param {Observable} o The Observable to release
20190          * @static
20191          */
20192         releaseCapture: function(o) {
20193             o.fireEvent = this.prototype.fireEvent;
20194         },
20195
20196         /**
20197          * Starts capture on the specified Observable. All events will be passed
20198          * to the supplied function with the event name + standard signature of the event
20199          * <b>before</b> the event is fired. If the supplied function returns false,
20200          * the event will not fire.
20201          * @param {Observable} o The Observable to capture events from.
20202          * @param {Function} fn The function to call when an event is fired.
20203          * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Observable firing the event.
20204          * @static
20205          */
20206         capture: function(o, fn, scope) {
20207             o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
20208         },
20209
20210         /**
20211 Sets observability on the passed class constructor.
20212
20213 This makes any event fired on any instance of the passed class also fire a single event through
20214 the __class__ allowing for central handling of events on many instances at once.
20215
20216 Usage:
20217
20218     Ext.util.Observable.observe(Ext.data.Connection);
20219     Ext.data.Connection.on('beforerequest', function(con, options) {
20220         console.log('Ajax request made to ' + options.url);
20221     });
20222
20223          * @param {Function} c The class constructor to make observable.
20224          * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
20225          * @static
20226          * @markdown
20227          */
20228         observe: function(cls, listeners) {
20229             if (cls) {
20230                 if (!cls.isObservable) {
20231                     Ext.applyIf(cls, new this());
20232                     this.capture(cls.prototype, cls.fireEvent, cls);
20233                 }
20234                 if (Ext.isObject(listeners)) {
20235                     cls.on(listeners);
20236                 }
20237                 return cls;
20238             }
20239         }
20240     },
20241
20242     /* End Definitions */
20243
20244     /**
20245     * @cfg {Object} listeners (optional) <p>A config object containing one or more event handlers to be added to this
20246     * object during initialization.  This should be a valid listeners config object as specified in the
20247     * {@link #addListener} example for attaching multiple handlers at once.</p>
20248     * <br><p><b><u>DOM events from ExtJs {@link Ext.Component Components}</u></b></p>
20249     * <br><p>While <i>some</i> ExtJs Component classes export selected DOM events (e.g. "click", "mouseover" etc), this
20250     * is usually only done when extra value can be added. For example the {@link Ext.view.View DataView}'s
20251     * <b><code>{@link Ext.view.View#click click}</code></b> event passing the node clicked on. To access DOM
20252     * events directly from a child element of a Component, we need to specify the <code>element</code> option to
20253     * identify the Component property to add a DOM listener to:
20254     * <pre><code>
20255 new Ext.panel.Panel({
20256     width: 400,
20257     height: 200,
20258     dockedItems: [{
20259         xtype: 'toolbar'
20260     }],
20261     listeners: {
20262         click: {
20263             element: 'el', //bind to the underlying el property on the panel
20264             fn: function(){ console.log('click el'); }
20265         },
20266         dblclick: {
20267             element: 'body', //bind to the underlying body property on the panel
20268             fn: function(){ console.log('dblclick body'); }
20269         }
20270     }
20271 });
20272 </code></pre>
20273     * </p>
20274     */
20275     // @private
20276     isObservable: true,
20277
20278     constructor: function(config) {
20279         var me = this;
20280
20281         Ext.apply(me, config);
20282         if (me.listeners) {
20283             me.on(me.listeners);
20284             delete me.listeners;
20285         }
20286         me.events = me.events || {};
20287
20288         if (me.bubbleEvents) {
20289             me.enableBubble(me.bubbleEvents);
20290         }
20291     },
20292
20293     // @private
20294     eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal)$/,
20295
20296     /**
20297      * <p>Adds listeners to any Observable object (or Element) which are automatically removed when this Component
20298      * is destroyed.
20299      * @param {Observable/Element} item The item to which to add a listener/listeners.
20300      * @param {Object/String} ename The event name, or an object containing event name properties.
20301      * @param {Function} fn Optional. If the <code>ename</code> parameter was an event name, this
20302      * is the handler function.
20303      * @param {Object} scope Optional. If the <code>ename</code> parameter was an event name, this
20304      * is the scope (<code>this</code> reference) in which the handler function is executed.
20305      * @param {Object} opt Optional. If the <code>ename</code> parameter was an event name, this
20306      * is the {@link Ext.util.Observable#addListener addListener} options.
20307      */
20308     addManagedListener : function(item, ename, fn, scope, options) {
20309         var me = this,
20310             managedListeners = me.managedListeners = me.managedListeners || [],
20311             config;
20312
20313         if (Ext.isObject(ename)) {
20314             options = ename;
20315             for (ename in options) {
20316                 if (options.hasOwnProperty(ename)) {
20317                     config = options[ename];
20318                     if (!me.eventOptionsRe.test(ename)) {
20319                         me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
20320                     }
20321                 }
20322             }
20323         }
20324         else {
20325             managedListeners.push({
20326                 item: item,
20327                 ename: ename,
20328                 fn: fn,
20329                 scope: scope,
20330                 options: options
20331             });
20332
20333             item.on(ename, fn, scope, options);
20334         }
20335     },
20336
20337     /**
20338      * Removes listeners that were added by the {@link #mon} method.
20339      * @param {Observable|Element} item The item from which to remove a listener/listeners.
20340      * @param {Object|String} ename The event name, or an object containing event name properties.
20341      * @param {Function} fn Optional. If the <code>ename</code> parameter was an event name, this
20342      * is the handler function.
20343      * @param {Object} scope Optional. If the <code>ename</code> parameter was an event name, this
20344      * is the scope (<code>this</code> reference) in which the handler function is executed.
20345      */
20346      removeManagedListener : function(item, ename, fn, scope) {
20347         var me = this,
20348             options,
20349             config,
20350             managedListeners,
20351             length,
20352             i;
20353
20354         if (Ext.isObject(ename)) {
20355             options = ename;
20356             for (ename in options) {
20357                 if (options.hasOwnProperty(ename)) {
20358                     config = options[ename];
20359                     if (!me.eventOptionsRe.test(ename)) {
20360                         me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
20361                     }
20362                 }
20363             }
20364         }
20365
20366         managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
20367
20368         for (i = 0, length = managedListeners.length; i < length; i++) {
20369             me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn, scope);
20370         }
20371     },
20372
20373     /**
20374      * <p>Fires the specified event with the passed parameters (minus the event name).</p>
20375      * <p>An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget})
20376      * by calling {@link #enableBubble}.</p>
20377      * @param {String} eventName The name of the event to fire.
20378      * @param {Object...} args Variable number of parameters are passed to handlers.
20379      * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
20380      */
20381     fireEvent: function() {
20382         var me = this,
20383             args = Ext.Array.toArray(arguments),
20384             ename = args[0].toLowerCase(),
20385             ret = true,
20386             event = me.events[ename],
20387             queue = me.eventQueue,
20388             parent;
20389
20390         if (me.eventsSuspended === true) {
20391             if (queue) {
20392                 queue.push(args);
20393             }
20394         } else if (event && Ext.isObject(event) && event.bubble) {
20395             if (event.fire.apply(event, args.slice(1)) === false) {
20396                 return false;
20397             }
20398             parent = me.getBubbleTarget && me.getBubbleTarget();
20399             if (parent && parent.isObservable) {
20400                 if (!parent.events[ename] || !Ext.isObject(parent.events[ename]) || !parent.events[ename].bubble) {
20401                     parent.enableBubble(ename);
20402                 }
20403                 return parent.fireEvent.apply(parent, args);
20404             }
20405         } else if (event && Ext.isObject(event)) {
20406             args.shift();
20407             ret = event.fire.apply(event, args);
20408         }
20409         return ret;
20410     },
20411
20412     /**
20413      * Appends an event handler to this object.
20414      * @param {String}   eventName The name of the event to listen for. May also be an object who's property names are event names. See
20415      * @param {Function} handler The method the event invokes.
20416      * @param {Object}   scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
20417      * <b>If omitted, defaults to the object which fired the event.</b>
20418      * @param {Object}   options (optional) An object containing handler configuration.
20419      * properties. This may contain any of the following properties:<ul>
20420      * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
20421      * <b>If omitted, defaults to the object which fired the event.</b></div></li>
20422      * <li><b>delay</b> : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after the event fires.</div></li>
20423      * <li><b>single</b> : 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>
20424      * <li><b>buffer</b> : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
20425      * by the specified number of milliseconds. If the event fires again within that time, the original
20426      * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
20427      * <li><b>target</b> : Observable<div class="sub-desc">Only call the handler if the event was fired on the target Observable, <i>not</i>
20428      * if the event was bubbled up from a child Observable.</div></li>
20429      * <li><b>element</b> : String<div class="sub-desc"><b>This option is only valid for listeners bound to {@link Ext.Component Components}.</b>
20430      * The name of a Component property which references an element to add a listener to.
20431      * <p>This option is useful during Component construction to add DOM event listeners to elements of {@link Ext.Component Components} which
20432      * will exist only after the Component is rendered. For example, to add a click listener to a Panel's body:<pre><code>
20433 new Ext.panel.Panel({
20434     title: 'The title',
20435     listeners: {
20436         click: this.handlePanelClick,
20437         element: 'body'
20438     }
20439 });
20440 </code></pre></p>
20441      * <p>When added in this way, the options available are the options applicable to {@link Ext.core.Element#addListener}</p></div></li>
20442      * </ul><br>
20443      * <p>
20444      * <b>Combining Options</b><br>
20445      * Using the options argument, it is possible to combine different types of listeners:<br>
20446      * <br>
20447      * A delayed, one-time listener.
20448      * <pre><code>
20449 myPanel.on('hide', this.handleClick, this, {
20450 single: true,
20451 delay: 100
20452 });</code></pre>
20453      * <p>
20454      * <b>Attaching multiple handlers in 1 call</b><br>
20455      * The method also allows for a single argument to be passed which is a config object containing properties
20456      * which specify multiple events. For example:<pre><code>
20457 myGridPanel.on({
20458     cellClick: this.onCellClick,
20459     mouseover: this.onMouseOver,
20460     mouseout: this.onMouseOut,
20461     scope: this // Important. Ensure "this" is correct during handler execution
20462 });
20463 </code></pre>.
20464      * <p>
20465      */
20466     addListener: function(ename, fn, scope, options) {
20467         var me = this,
20468             config,
20469             event;
20470
20471         if (Ext.isObject(ename)) {
20472             options = ename;
20473             for (ename in options) {
20474                 if (options.hasOwnProperty(ename)) {
20475                     config = options[ename];
20476                     if (!me.eventOptionsRe.test(ename)) {
20477                         me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
20478                     }
20479                 }
20480             }
20481         }
20482         else {
20483             ename = ename.toLowerCase();
20484             me.events[ename] = me.events[ename] || true;
20485             event = me.events[ename] || true;
20486             if (Ext.isBoolean(event)) {
20487                 me.events[ename] = event = new Ext.util.Event(me, ename);
20488             }
20489             event.addListener(fn, scope, Ext.isObject(options) ? options : {});
20490         }
20491     },
20492
20493     /**
20494      * Removes an event handler.
20495      * @param {String}   eventName The type of event the handler was associated with.
20496      * @param {Function} handler   The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
20497      * @param {Object}   scope     (optional) The scope originally specified for the handler.
20498      */
20499     removeListener: function(ename, fn, scope) {
20500         var me = this,
20501             config,
20502             event,
20503             options;
20504
20505         if (Ext.isObject(ename)) {
20506             options = ename;
20507             for (ename in options) {
20508                 if (options.hasOwnProperty(ename)) {
20509                     config = options[ename];
20510                     if (!me.eventOptionsRe.test(ename)) {
20511                         me.removeListener(ename, config.fn || config, config.scope || options.scope);
20512                     }
20513                 }
20514             }
20515         } else {
20516             ename = ename.toLowerCase();
20517             event = me.events[ename];
20518             if (event && event.isEvent) {
20519                 event.removeListener(fn, scope);
20520             }
20521         }
20522     },
20523
20524     /**
20525      * Removes all listeners for this object including the managed listeners
20526      */
20527     clearListeners: function() {
20528         var events = this.events,
20529             event,
20530             key;
20531
20532         for (key in events) {
20533             if (events.hasOwnProperty(key)) {
20534                 event = events[key];
20535                 if (event.isEvent) {
20536                     event.clearListeners();
20537                 }
20538             }
20539         }
20540
20541         this.clearManagedListeners();
20542     },
20543
20544     purgeListeners : function() {
20545         if (Ext.global.console) {
20546             Ext.global.console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
20547         }
20548         return this.clearListeners.apply(this, arguments);
20549     },
20550
20551     /**
20552      * Removes all managed listeners for this object.
20553      */
20554     clearManagedListeners : function() {
20555         var managedListeners = this.managedListeners || [],
20556             i = 0,
20557             len = managedListeners.length;
20558
20559         for (; i < len; i++) {
20560             this.removeManagedListenerItem(true, managedListeners[i]);
20561         }
20562
20563         this.managedListeners = [];
20564     },
20565     
20566     /**
20567      * Remove a single managed listener item
20568      * @private
20569      * @param {Boolean} isClear True if this is being called during a clear
20570      * @param {Object} managedListener The managed listener item
20571      * See removeManagedListener for other args
20572      */
20573     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
20574         if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
20575             managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
20576             if (!isClear) {
20577                 Ext.Array.remove(this.managedListeners, managedListener);
20578             }    
20579         }
20580     },
20581
20582     purgeManagedListeners : function() {
20583         if (Ext.global.console) {
20584             Ext.global.console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
20585         }
20586         return this.clearManagedListeners.apply(this, arguments);
20587     },
20588
20589     /**
20590      * Adds the specified events to the list of events which this Observable may fire.
20591      * @param {Object/String} o Either an object with event names as properties with a value of <code>true</code>
20592      * or the first event name string if multiple event names are being passed as separate parameters.
20593      * @param {String} [additional] Optional additional event names if multiple event names are being passed as separate parameters.
20594      * Usage:<pre><code>
20595 this.addEvents('storeloaded', 'storecleared');
20596 </code></pre>
20597      */
20598     addEvents: function(o) {
20599         var me = this,
20600             args,
20601             len,
20602             i;
20603             
20604             me.events = me.events || {};
20605         if (Ext.isString(o)) {
20606             args = arguments;
20607             i = args.length;
20608             
20609             while (i--) {
20610                 me.events[args[i]] = me.events[args[i]] || true;
20611             }
20612         } else {
20613             Ext.applyIf(me.events, o);
20614         }
20615     },
20616
20617     /**
20618      * Checks to see if this object has any listeners for a specified event
20619      * @param {String} eventName The name of the event to check for
20620      * @return {Boolean} True if the event is being listened for, else false
20621      */
20622     hasListener: function(ename) {
20623         var event = this.events[ename.toLowerCase()];
20624         return event && event.isEvent === true && event.listeners.length > 0;
20625     },
20626
20627     /**
20628      * Suspend the firing of all events. (see {@link #resumeEvents})
20629      * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
20630      * after the {@link #resumeEvents} call instead of discarding all suspended events;
20631      */
20632     suspendEvents: function(queueSuspended) {
20633         this.eventsSuspended = true;
20634         if (queueSuspended && !this.eventQueue) {
20635             this.eventQueue = [];
20636         }
20637     },
20638
20639     /**
20640      * Resume firing events. (see {@link #suspendEvents})
20641      * If events were suspended using the <code><b>queueSuspended</b></code> parameter, then all
20642      * events fired during event suspension will be sent to any listeners now.
20643      */
20644     resumeEvents: function() {
20645         var me = this,
20646             queued = me.eventQueue || [];
20647
20648         me.eventsSuspended = false;
20649         delete me.eventQueue;
20650
20651         Ext.each(queued,
20652         function(e) {
20653             me.fireEvent.apply(me, e);
20654         });
20655     },
20656
20657     /**
20658      * Relays selected events from the specified Observable as if the events were fired by <code><b>this</b></code>.
20659      * @param {Object} origin The Observable whose events this object is to relay.
20660      * @param {Array} events Array of event names to relay.
20661      */
20662     relayEvents : function(origin, events, prefix) {
20663         prefix = prefix || '';
20664         var me = this,
20665             len = events.length,
20666             i = 0,
20667             oldName,
20668             newName;
20669
20670         for (; i < len; i++) {
20671             oldName = events[i].substr(prefix.length);
20672             newName = prefix + oldName;
20673             me.events[newName] = me.events[newName] || true;
20674             origin.on(oldName, me.createRelayer(newName));
20675         }
20676     },
20677
20678     /**
20679      * @private
20680      * Creates an event handling function which refires the event from this object as the passed event name.
20681      * @param newName
20682      * @returns {Function}
20683      */
20684     createRelayer: function(newName){
20685         var me = this;
20686         return function(){
20687             return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.call(arguments, 0, -1)));
20688         };
20689     },
20690
20691     /**
20692      * <p>Enables events fired by this Observable to bubble up an owner hierarchy by calling
20693      * <code>this.getBubbleTarget()</code> if present. There is no implementation in the Observable base class.</p>
20694      * <p>This is commonly used by Ext.Components to bubble events to owner Containers. See {@link Ext.Component#getBubbleTarget}. The default
20695      * implementation in Ext.Component returns the Component's immediate owner. But if a known target is required, this can be overridden to
20696      * access the required target more quickly.</p>
20697      * <p>Example:</p><pre><code>
20698 Ext.override(Ext.form.field.Base, {
20699 //  Add functionality to Field&#39;s initComponent to enable the change event to bubble
20700 initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
20701     this.enableBubble('change');
20702 }),
20703
20704 //  We know that we want Field&#39;s events to bubble directly to the FormPanel.
20705 getBubbleTarget : function() {
20706     if (!this.formPanel) {
20707         this.formPanel = this.findParentByType('form');
20708     }
20709     return this.formPanel;
20710 }
20711 });
20712
20713 var myForm = new Ext.formPanel({
20714 title: 'User Details',
20715 items: [{
20716     ...
20717 }],
20718 listeners: {
20719     change: function() {
20720         // Title goes red if form has been modified.
20721         myForm.header.setStyle('color', 'red');
20722     }
20723 }
20724 });
20725 </code></pre>
20726      * @param {String/Array} events The event name to bubble, or an Array of event names.
20727      */
20728     enableBubble: function(events) {
20729         var me = this;
20730         if (!Ext.isEmpty(events)) {
20731             events = Ext.isArray(events) ? events: Ext.Array.toArray(arguments);
20732             Ext.each(events,
20733             function(ename) {
20734                 ename = ename.toLowerCase();
20735                 var ce = me.events[ename] || true;
20736                 if (Ext.isBoolean(ce)) {
20737                     ce = new Ext.util.Event(me, ename);
20738                     me.events[ename] = ce;
20739                 }
20740                 ce.bubble = true;
20741             });
20742         }
20743     }
20744 }, function() {
20745     /**
20746      * Removes an event handler (shorthand for {@link #removeListener}.)
20747      * @param {String}   eventName     The type of event the handler was associated with.
20748      * @param {Function} handler       The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
20749      * @param {Object}   scope         (optional) The scope originally specified for the handler.
20750      * @method un
20751      */
20752
20753     /**
20754      * Appends an event handler to this object (shorthand for {@link #addListener}.)
20755      * @param {String}   eventName     The type of event to listen for
20756      * @param {Function} handler       The method the event invokes
20757      * @param {Object}   scope         (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
20758      * <b>If omitted, defaults to the object which fired the event.</b>
20759      * @param {Object}   options       (optional) An object containing handler configuration.
20760      * @method on
20761      */
20762
20763     this.createAlias({
20764         on: 'addListener',
20765         un: 'removeListener',
20766         mon: 'addManagedListener',
20767         mun: 'removeManagedListener'
20768     });
20769
20770     //deprecated, will be removed in 5.0
20771     this.observeClass = this.observe;
20772
20773     Ext.apply(Ext.util.Observable.prototype, function(){
20774         // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
20775         // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
20776         // private
20777         function getMethodEvent(method){
20778             var e = (this.methodEvents = this.methodEvents || {})[method],
20779                 returnValue,
20780                 v,
20781                 cancel,
20782                 obj = this;
20783
20784             if (!e) {
20785                 this.methodEvents[method] = e = {};
20786                 e.originalFn = this[method];
20787                 e.methodName = method;
20788                 e.before = [];
20789                 e.after = [];
20790
20791                 var makeCall = function(fn, scope, args){
20792                     if((v = fn.apply(scope || obj, args)) !== undefined){
20793                         if (typeof v == 'object') {
20794                             if(v.returnValue !== undefined){
20795                                 returnValue = v.returnValue;
20796                             }else{
20797                                 returnValue = v;
20798                             }
20799                             cancel = !!v.cancel;
20800                         }
20801                         else
20802                             if (v === false) {
20803                                 cancel = true;
20804                             }
20805                             else {
20806                                 returnValue = v;
20807                             }
20808                     }
20809                 };
20810
20811                 this[method] = function(){
20812                     var args = Array.prototype.slice.call(arguments, 0),
20813                         b, i, len;
20814                     returnValue = v = undefined;
20815                     cancel = false;
20816
20817                     for(i = 0, len = e.before.length; i < len; i++){
20818                         b = e.before[i];
20819                         makeCall(b.fn, b.scope, args);
20820                         if (cancel) {
20821                             return returnValue;
20822                         }
20823                     }
20824
20825                     if((v = e.originalFn.apply(obj, args)) !== undefined){
20826                         returnValue = v;
20827                     }
20828
20829                     for(i = 0, len = e.after.length; i < len; i++){
20830                         b = e.after[i];
20831                         makeCall(b.fn, b.scope, args);
20832                         if (cancel) {
20833                             return returnValue;
20834                         }
20835                     }
20836                     return returnValue;
20837                 };
20838             }
20839             return e;
20840         }
20841
20842         return {
20843             // these are considered experimental
20844             // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
20845             // adds an 'interceptor' called before the original method
20846             beforeMethod : function(method, fn, scope){
20847                 getMethodEvent.call(this, method).before.push({
20848                     fn: fn,
20849                     scope: scope
20850                 });
20851             },
20852
20853             // adds a 'sequence' called after the original method
20854             afterMethod : function(method, fn, scope){
20855                 getMethodEvent.call(this, method).after.push({
20856                     fn: fn,
20857                     scope: scope
20858                 });
20859             },
20860
20861             removeMethodListener: function(method, fn, scope){
20862                 var e = this.getMethodEvent(method),
20863                     i, len;
20864                 for(i = 0, len = e.before.length; i < len; i++){
20865                     if(e.before[i].fn == fn && e.before[i].scope == scope){
20866                         e.before.splice(i, 1);
20867                         return;
20868                     }
20869                 }
20870                 for(i = 0, len = e.after.length; i < len; i++){
20871                     if(e.after[i].fn == fn && e.after[i].scope == scope){
20872                         e.after.splice(i, 1);
20873                         return;
20874                     }
20875                 }
20876             },
20877
20878             toggleEventLogging: function(toggle) {
20879                 Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
20880                     if (Ext.isDefined(Ext.global.console)) {
20881                         Ext.global.console.log(en, arguments);
20882                     }
20883                 });
20884             }
20885         };
20886     }());
20887 });
20888
20889 /**
20890  * @class Ext.util.Animate
20891  * This animation class is a mixin.
20892  * 
20893  * Ext.util.Animate provides an API for the creation of animated transitions of properties and styles.  
20894  * This class is used as a mixin and currently applied to {@link Ext.core.Element}, {@link Ext.CompositeElement}, 
20895  * {@link Ext.draw.Sprite}, {@link Ext.draw.CompositeSprite}, and {@link Ext.Component}.  Note that Components 
20896  * have a limited subset of what attributes can be animated such as top, left, x, y, height, width, and 
20897  * opacity (color, paddings, and margins can not be animated).
20898  * 
20899  * ## Animation Basics
20900  * 
20901  * All animations require three things - `easing`, `duration`, and `to` (the final end value for each property) 
20902  * you wish to animate. Easing and duration are defaulted values specified below.
20903  * Easing describes how the intermediate values used during a transition will be calculated. 
20904  * {@link Ext.fx.Anim#easing Easing} allows for a transition to change speed over its duration.
20905  * You may use the defaults for easing and duration, but you must always set a 
20906  * {@link Ext.fx.Anim#to to} property which is the end value for all animations.  
20907  * 
20908  * Popular element 'to' configurations are:
20909  * 
20910  *  - opacity
20911  *  - x
20912  *  - y
20913  *  - color
20914  *  - height
20915  *  - width 
20916  * 
20917  * Popular sprite 'to' configurations are:
20918  * 
20919  *  - translation
20920  *  - path
20921  *  - scale
20922  *  - stroke
20923  *  - rotation
20924  * 
20925  * The default duration for animations is 250 (which is a 1/4 of a second).  Duration is denoted in 
20926  * milliseconds.  Therefore 1 second is 1000, 1 minute would be 60000, and so on. The default easing curve 
20927  * used for all animations is 'ease'.  Popular easing functions are included and can be found in {@link Ext.fx.Anim#easing Easing}.
20928  * 
20929  * For example, a simple animation to fade out an element with a default easing and duration:
20930  * 
20931  *     var p1 = Ext.get('myElementId');
20932  * 
20933  *     p1.animate({
20934  *         to: {
20935  *             opacity: 0
20936  *         }
20937  *     });
20938  * 
20939  * To make this animation fade out in a tenth of a second:
20940  * 
20941  *     var p1 = Ext.get('myElementId');
20942  * 
20943  *     p1.animate({
20944  *        duration: 100,
20945  *         to: {
20946  *             opacity: 0
20947  *         }
20948  *     });
20949  * 
20950  * ## Animation Queues
20951  * 
20952  * By default all animations are added to a queue which allows for animation via a chain-style API.
20953  * For example, the following code will queue 4 animations which occur sequentially (one right after the other):
20954  * 
20955  *     p1.animate({
20956  *         to: {
20957  *             x: 500
20958  *         }
20959  *     }).animate({
20960  *         to: {
20961  *             y: 150
20962  *         }
20963  *     }).animate({
20964  *         to: {
20965  *             backgroundColor: '#f00'  //red
20966  *         }
20967  *     }).animate({
20968  *         to: {
20969  *             opacity: 0
20970  *         }
20971  *     });
20972  * 
20973  * You can change this behavior by calling the {@link Ext.util.Animate#syncFx syncFx} method and all 
20974  * subsequent animations for the specified target will be run concurrently (at the same time).
20975  * 
20976  *     p1.syncFx();  //this will make all animations run at the same time
20977  * 
20978  *     p1.animate({
20979  *         to: {
20980  *             x: 500
20981  *         }
20982  *     }).animate({
20983  *         to: {
20984  *             y: 150
20985  *         }
20986  *     }).animate({
20987  *         to: {
20988  *             backgroundColor: '#f00'  //red
20989  *         }
20990  *     }).animate({
20991  *         to: {
20992  *             opacity: 0
20993  *         }
20994  *     });
20995  * 
20996  * This works the same as:
20997  * 
20998  *     p1.animate({
20999  *         to: {
21000  *             x: 500,
21001  *             y: 150,
21002  *             backgroundColor: '#f00'  //red
21003  *             opacity: 0
21004  *         }
21005  *     });
21006  * 
21007  * The {@link Ext.util.Animate#stopAnimation stopAnimation} method can be used to stop any 
21008  * currently running animations and clear any queued animations. 
21009  * 
21010  * ## Animation Keyframes
21011  *
21012  * You can also set up complex animations with {@link Ext.fx.Anim#keyframe keyframe} which follows the 
21013  * CSS3 Animation configuration pattern. Note rotation, translation, and scaling can only be done for sprites. 
21014  * The previous example can be written with the following syntax:
21015  * 
21016  *     p1.animate({
21017  *         duration: 1000,  //one second total
21018  *         keyframes: {
21019  *             25: {     //from 0 to 250ms (25%)
21020  *                 x: 0
21021  *             },
21022  *             50: {   //from 250ms to 500ms (50%)
21023  *                 y: 0
21024  *             },
21025  *             75: {  //from 500ms to 750ms (75%)
21026  *                 backgroundColor: '#f00'  //red
21027  *             },
21028  *             100: {  //from 750ms to 1sec
21029  *                 opacity: 0
21030  *             }
21031  *         }
21032  *     });
21033  * 
21034  * ## Animation Events
21035  * 
21036  * Each animation you create has events for {@link Ext.fx.Anim#beforeanimation beforeanimation}, 
21037  * {@link Ext.fx.Anim#afteranimate afteranimate}, and {@link Ext.fx.Anim#lastframe lastframe}.  
21038  * Keyframed animations adds an additional {@link Ext.fx.Animator#keyframe keyframe} event which 
21039  * fires for each keyframe in your animation.
21040  * 
21041  * All animations support the {@link Ext.util.Observable#listeners listeners} configuration to attact functions to these events.
21042  *    
21043  *     startAnimate: function() {
21044  *         var p1 = Ext.get('myElementId');
21045  *         p1.animate({
21046  *            duration: 100,
21047  *             to: {
21048  *                 opacity: 0
21049  *             },
21050  *             listeners: {
21051  *                 beforeanimate:  function() {
21052  *                     // Execute my custom method before the animation
21053  *                     this.myBeforeAnimateFn();
21054  *                 },
21055  *                 afteranimate: function() {
21056  *                     // Execute my custom method after the animation
21057  *                     this.myAfterAnimateFn();
21058  *                 },
21059  *                 scope: this
21060  *         });
21061  *     },
21062  *     myBeforeAnimateFn: function() {
21063  *       // My custom logic
21064  *     },
21065  *     myAfterAnimateFn: function() {
21066  *       // My custom logic
21067  *     }
21068  * 
21069  * Due to the fact that animations run asynchronously, you can determine if an animation is currently 
21070  * running on any target by using the {@link Ext.util.Animate#getActiveAnimation getActiveAnimation} 
21071  * method.  This method will return false if there are no active animations or return the currently 
21072  * running {@link Ext.fx.Anim} instance.
21073  * 
21074  * In this example, we're going to wait for the current animation to finish, then stop any other 
21075  * queued animations before we fade our element's opacity to 0:
21076  * 
21077  *     var curAnim = p1.getActiveAnimation();
21078  *     if (curAnim) {
21079  *         curAnim.on('afteranimate', function() {
21080  *             p1.stopAnimation();
21081  *             p1.animate({
21082  *                 to: {
21083  *                     opacity: 0
21084  *                 }
21085  *             });
21086  *         });
21087  *     }
21088  * 
21089  * @docauthor Jamie Avins <jamie@sencha.com>
21090  */
21091 Ext.define('Ext.util.Animate', {
21092
21093     uses: ['Ext.fx.Manager', 'Ext.fx.Anim'],
21094
21095     /**
21096      * <p>Perform custom animation on this object.<p>
21097      * <p>This method is applicable to both the the {@link Ext.Component Component} class and the {@link Ext.core.Element Element} class.
21098      * It performs animated transitions of certain properties of this object over a specified timeline.</p>
21099      * <p>The sole parameter is an object which specifies start property values, end property values, and properties which
21100      * describe the timeline. Of the properties listed below, only <b><code>to</code></b> is mandatory.</p>
21101      * <p>Properties include<ul>
21102      * <li><code>from</code> <div class="sub-desc">An object which specifies start values for the properties being animated.
21103      * If not supplied, properties are animated from current settings. The actual properties which may be animated depend upon
21104      * ths object being animated. See the sections below on Element and Component animation.<div></li>
21105      * <li><code>to</code> <div class="sub-desc">An object which specifies end values for the properties being animated.</div></li>
21106      * <li><code>duration</code><div class="sub-desc">The duration <b>in milliseconds</b> for which the animation will run.</div></li>
21107      * <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>
21108      * <li>ease</li>
21109      * <li>easeIn</li>
21110      * <li>easeOut</li>
21111      * <li>easeInOut</li>
21112      * <li>backIn</li>
21113      * <li>backOut</li>
21114      * <li>elasticIn</li>
21115      * <li>elasticOut</li>
21116      * <li>bounceIn</li>
21117      * <li>bounceOut</li>
21118      * </ul></code></div></li>
21119      * <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.
21120      * 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>
21121      * <li><code>listeners</code> <div class="sub-desc">This is a standard {@link Ext.util.Observable#listeners listeners} configuration object which may be used
21122      * to inject behaviour at either the <code>beforeanimate</code> event or the <code>afteranimate</code> event.</div></li>
21123      * </ul></p>
21124      * <h3>Animating an {@link Ext.core.Element Element}</h3>
21125      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
21126      * <li><code>x</code> <div class="sub-desc">The page X position in pixels.</div></li>
21127      * <li><code>y</code> <div class="sub-desc">The page Y position in pixels</div></li>
21128      * <li><code>left</code> <div class="sub-desc">The element's CSS <code>left</code> value. Units must be supplied.</div></li>
21129      * <li><code>top</code> <div class="sub-desc">The element's CSS <code>top</code> value. Units must be supplied.</div></li>
21130      * <li><code>width</code> <div class="sub-desc">The element's CSS <code>width</code> value. Units must be supplied.</div></li>
21131      * <li><code>height</code> <div class="sub-desc">The element's CSS <code>height</code> value. Units must be supplied.</div></li>
21132      * <li><code>scrollLeft</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
21133      * <li><code>scrollTop</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
21134      * <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>
21135      * </ul>
21136      * <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
21137      * 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
21138      * directly animate certain properties of Components.</b></p>
21139      * <h3>Animating a {@link Ext.Component Component}</h3>
21140      * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
21141      * <li><code>x</code> <div class="sub-desc">The Component's page X position in pixels.</div></li>
21142      * <li><code>y</code> <div class="sub-desc">The Component's page Y position in pixels</div></li>
21143      * <li><code>left</code> <div class="sub-desc">The Component's <code>left</code> value in pixels.</div></li>
21144      * <li><code>top</code> <div class="sub-desc">The Component's <code>top</code> value in pixels.</div></li>
21145      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
21146      * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
21147      * <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
21148      * of the animation. <i>Use sparingly as laying out on every intermediate size change is an expensive operation</i>.</div></li>
21149      * </ul>
21150      * <p>For example, to animate a Window to a new size, ensuring that its internal layout, and any shadow is correct:</p>
21151      * <pre><code>
21152 myWindow = Ext.create('Ext.window.Window', {
21153     title: 'Test Component animation',
21154     width: 500,
21155     height: 300,
21156     layout: {
21157         type: 'hbox',
21158         align: 'stretch'
21159     },
21160     items: [{
21161         title: 'Left: 33%',
21162         margins: '5 0 5 5',
21163         flex: 1
21164     }, {
21165         title: 'Left: 66%',
21166         margins: '5 5 5 5',
21167         flex: 2
21168     }]
21169 });
21170 myWindow.show();
21171 myWindow.header.el.on('click', function() {
21172     myWindow.animate({
21173         to: {
21174             width: (myWindow.getWidth() == 500) ? 700 : 500,
21175             height: (myWindow.getHeight() == 300) ? 400 : 300,
21176         }
21177     });
21178 });
21179 </code></pre>
21180      * <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
21181      * 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>
21182      * @param {Object} config An object containing properties which describe the animation's start and end states, and the timeline of the animation.
21183      * @return {Object} this
21184      */
21185     animate: function(animObj) {
21186         var me = this;
21187         if (Ext.fx.Manager.hasFxBlock(me.id)) {
21188             return me;
21189         }
21190         Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(animObj)));
21191         return this;
21192     },
21193
21194     // @private - process the passed fx configuration.
21195     anim: function(config) {
21196         if (!Ext.isObject(config)) {
21197             return (config) ? {} : false;
21198         }
21199
21200         var me = this;
21201
21202         if (config.stopAnimation) {
21203             me.stopAnimation();
21204         }
21205
21206         Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
21207
21208         return Ext.apply({
21209             target: me,
21210             paused: true
21211         }, config);
21212     },
21213
21214     /**
21215      * @deprecated 4.0 Replaced by {@link #stopAnimation}
21216      * Stops any running effects and clears this object's internal effects queue if it contains
21217      * any additional effects that haven't started yet.
21218      * @return {Ext.core.Element} The Element
21219      * @method
21220      */
21221     stopFx: Ext.Function.alias(Ext.util.Animate, 'stopAnimation'),
21222
21223     /**
21224      * Stops any running effects and clears this object's internal effects queue if it contains
21225      * any additional effects that haven't started yet.
21226      * @return {Ext.core.Element} The Element
21227      */
21228     stopAnimation: function() {
21229         Ext.fx.Manager.stopAnimation(this.id);
21230         return this;
21231     },
21232
21233     /**
21234      * Ensures that all effects queued after syncFx is called on this object are
21235      * run concurrently.  This is the opposite of {@link #sequenceFx}.
21236      * @return {Object} this
21237      */
21238     syncFx: function() {
21239         Ext.fx.Manager.setFxDefaults(this.id, {
21240             concurrent: true
21241         });
21242         return this;
21243     },
21244
21245     /**
21246      * Ensures that all effects queued after sequenceFx is called on this object are
21247      * run in sequence.  This is the opposite of {@link #syncFx}.
21248      * @return {Object} this
21249      */
21250     sequenceFx: function() {
21251         Ext.fx.Manager.setFxDefaults(this.id, {
21252             concurrent: false
21253         });
21254         return this;
21255     },
21256
21257     /**
21258      * @deprecated 4.0 Replaced by {@link #getActiveAnimation}
21259      * Returns thq current animation if this object has any effects actively running or queued, else returns false.
21260      * @return {Mixed} anim if element has active effects, else false
21261      * @method
21262      */
21263     hasActiveFx: Ext.Function.alias(Ext.util.Animate, 'getActiveAnimation'),
21264
21265     /**
21266      * Returns thq current animation if this object has any effects actively running or queued, else returns false.
21267      * @return {Mixed} anim if element has active effects, else false
21268      */
21269     getActiveAnimation: function() {
21270         return Ext.fx.Manager.getActiveAnimation(this.id);
21271     }
21272 });
21273
21274 // Apply Animate mixin manually until Element is defined in the proper 4.x way
21275 Ext.applyIf(Ext.core.Element.prototype, Ext.util.Animate.prototype);
21276 /**
21277  * @class Ext.state.Provider
21278  * <p>Abstract base class for state provider implementations. The provider is responsible
21279  * for setting values  and extracting values to/from the underlying storage source. The 
21280  * storage source can vary and the details should be implemented in a subclass. For example
21281  * a provider could use a server side database or the browser localstorage where supported.</p>
21282  *
21283  * <p>This class provides methods for encoding and decoding <b>typed</b> variables including 
21284  * dates and defines the Provider interface. By default these methods put the value and the
21285  * type information into a delimited string that can be stored. These should be overridden in 
21286  * a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
21287  */
21288 Ext.define('Ext.state.Provider', {
21289     mixins: {
21290         observable: 'Ext.util.Observable'
21291     },
21292     
21293     /**
21294      * @cfg {String} prefix A string to prefix to items stored in the underlying state store. 
21295      * Defaults to <tt>'ext-'</tt>
21296      */
21297     prefix: 'ext-',
21298     
21299     constructor : function(config){
21300         config = config || {};
21301         var me = this;
21302         Ext.apply(me, config);
21303         /**
21304          * @event statechange
21305          * Fires when a state change occurs.
21306          * @param {Provider} this This state provider
21307          * @param {String} key The state key which was changed
21308          * @param {String} value The encoded value for the state
21309          */
21310         me.addEvents("statechange");
21311         me.state = {};
21312         me.mixins.observable.constructor.call(me);
21313     },
21314     
21315     /**
21316      * Returns the current value for a key
21317      * @param {String} name The key name
21318      * @param {Mixed} defaultValue A default value to return if the key's value is not found
21319      * @return {Mixed} The state data
21320      */
21321     get : function(name, defaultValue){
21322         return typeof this.state[name] == "undefined" ?
21323             defaultValue : this.state[name];
21324     },
21325
21326     /**
21327      * Clears a value from the state
21328      * @param {String} name The key name
21329      */
21330     clear : function(name){
21331         var me = this;
21332         delete me.state[name];
21333         me.fireEvent("statechange", me, name, null);
21334     },
21335
21336     /**
21337      * Sets the value for a key
21338      * @param {String} name The key name
21339      * @param {Mixed} value The value to set
21340      */
21341     set : function(name, value){
21342         var me = this;
21343         me.state[name] = value;
21344         me.fireEvent("statechange", me, name, value);
21345     },
21346
21347     /**
21348      * Decodes a string previously encoded with {@link #encodeValue}.
21349      * @param {String} value The value to decode
21350      * @return {Mixed} The decoded value
21351      */
21352     decodeValue : function(value){
21353
21354         // a -> Array
21355         // n -> Number
21356         // d -> Date
21357         // b -> Boolean
21358         // s -> String
21359         // o -> Object
21360         // -> Empty (null)
21361
21362         var me = this,
21363             re = /^(a|n|d|b|s|o|e)\:(.*)$/,
21364             matches = re.exec(unescape(value)),
21365             all,
21366             type,
21367             value,
21368             keyValue;
21369             
21370         if(!matches || !matches[1]){
21371             return; // non state
21372         }
21373         
21374         type = matches[1];
21375         value = matches[2];
21376         switch (type) {
21377             case 'e':
21378                 return null;
21379             case 'n':
21380                 return parseFloat(value);
21381             case 'd':
21382                 return new Date(Date.parse(value));
21383             case 'b':
21384                 return (value == '1');
21385             case 'a':
21386                 all = [];
21387                 if(value != ''){
21388                     Ext.each(value.split('^'), function(val){
21389                         all.push(me.decodeValue(val));
21390                     }, me);
21391                 }
21392                 return all;
21393            case 'o':
21394                 all = {};
21395                 if(value != ''){
21396                     Ext.each(value.split('^'), function(val){
21397                         keyValue = val.split('=');
21398                         all[keyValue[0]] = me.decodeValue(keyValue[1]);
21399                     }, me);
21400                 }
21401                 return all;
21402            default:
21403                 return value;
21404         }
21405     },
21406
21407     /**
21408      * Encodes a value including type information.  Decode with {@link #decodeValue}.
21409      * @param {Mixed} value The value to encode
21410      * @return {String} The encoded value
21411      */
21412     encodeValue : function(value){
21413         var flat = '',
21414             i = 0,
21415             enc,
21416             len,
21417             key;
21418             
21419         if (value == null) {
21420             return 'e:1';    
21421         } else if(typeof value == 'number') {
21422             enc = 'n:' + value;
21423         } else if(typeof value == 'boolean') {
21424             enc = 'b:' + (value ? '1' : '0');
21425         } else if(Ext.isDate(value)) {
21426             enc = 'd:' + value.toGMTString();
21427         } else if(Ext.isArray(value)) {
21428             for (len = value.length; i < len; i++) {
21429                 flat += this.encodeValue(value[i]);
21430                 if (i != len - 1) {
21431                     flat += '^';
21432                 }
21433             }
21434             enc = 'a:' + flat;
21435         } else if (typeof value == 'object') {
21436             for (key in value) {
21437                 if (typeof value[key] != 'function' && value[key] !== undefined) {
21438                     flat += key + '=' + this.encodeValue(value[key]) + '^';
21439                 }
21440             }
21441             enc = 'o:' + flat.substring(0, flat.length-1);
21442         } else {
21443             enc = 's:' + value;
21444         }
21445         return escape(enc);
21446     }
21447 });
21448 /**
21449  * @class Ext.util.HashMap
21450  * <p>
21451  * Represents a collection of a set of key and value pairs. Each key in the HashMap
21452  * must be unique, the same key cannot exist twice. Access to items is provided via
21453  * the key only. Sample usage:
21454  * <pre><code>
21455 var map = new Ext.util.HashMap();
21456 map.add('key1', 1);
21457 map.add('key2', 2);
21458 map.add('key3', 3);
21459
21460 map.each(function(key, value, length){
21461     console.log(key, value, length);
21462 });
21463  * </code></pre>
21464  * </p>
21465  *
21466  * <p>The HashMap is an unordered class,
21467  * there is no guarantee when iterating over the items that they will be in any particular
21468  * order. If this is required, then use a {@link Ext.util.MixedCollection}.
21469  * </p>
21470  * @constructor
21471  * @param {Object} config The configuration options
21472  */
21473 Ext.define('Ext.util.HashMap', {
21474
21475     /**
21476      * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
21477      * A default is provided that returns the <b>id</b> property on the object. This function is only used
21478      * if the add method is called with a single argument.
21479      */
21480
21481     mixins: {
21482         observable: 'Ext.util.Observable'
21483     },
21484
21485     constructor: function(config) {
21486         var me = this;
21487
21488         me.addEvents(
21489             /**
21490              * @event add
21491              * Fires when a new item is added to the hash
21492              * @param {Ext.util.HashMap} this.
21493              * @param {String} key The key of the added item.
21494              * @param {Object} value The value of the added item.
21495              */
21496             'add',
21497             /**
21498              * @event clear
21499              * Fires when the hash is cleared.
21500              * @param {Ext.util.HashMap} this.
21501              */
21502             'clear',
21503             /**
21504              * @event remove
21505              * Fires when an item is removed from the hash.
21506              * @param {Ext.util.HashMap} this.
21507              * @param {String} key The key of the removed item.
21508              * @param {Object} value The value of the removed item.
21509              */
21510             'remove',
21511             /**
21512              * @event replace
21513              * Fires when an item is replaced in the hash.
21514              * @param {Ext.util.HashMap} this.
21515              * @param {String} key The key of the replaced item.
21516              * @param {Object} value The new value for the item.
21517              * @param {Object} old The old value for the item.
21518              */
21519             'replace'
21520         );
21521
21522         me.mixins.observable.constructor.call(me, config);
21523         me.clear(true);
21524     },
21525
21526     /**
21527      * Gets the number of items in the hash.
21528      * @return {Number} The number of items in the hash.
21529      */
21530     getCount: function() {
21531         return this.length;
21532     },
21533
21534     /**
21535      * Implementation for being able to extract the key from an object if only
21536      * a single argument is passed.
21537      * @private
21538      * @param {String} key The key
21539      * @param {Object} value The value
21540      * @return {Array} [key, value]
21541      */
21542     getData: function(key, value) {
21543         // if we have no value, it means we need to get the key from the object
21544         if (value === undefined) {
21545             value = key;
21546             key = this.getKey(value);
21547         }
21548
21549         return [key, value];
21550     },
21551
21552     /**
21553      * Extracts the key from an object. This is a default implementation, it may be overridden
21554      * @private
21555      * @param {Object} o The object to get the key from
21556      * @return {String} The key to use.
21557      */
21558     getKey: function(o) {
21559         return o.id;
21560     },
21561
21562     /**
21563      * Adds an item to the collection. Fires the {@link #add} event when complete.
21564      * @param {String} key <p>The key to associate with the item, or the new item.</p>
21565      * <p>If a {@link #getKey} implementation was specified for this HashMap,
21566      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
21567      * the HashMap will be able to <i>derive</i> the key for the new item.
21568      * In this case just pass the new item in this parameter.</p>
21569      * @param {Object} o The item to add.
21570      * @return {Object} The item added.
21571      */
21572     add: function(key, value) {
21573         var me = this,
21574             data;
21575
21576         if (arguments.length === 1) {
21577             value = key;
21578             key = me.getKey(value);
21579         }
21580
21581         if (me.containsKey(key)) {
21582             me.replace(key, value);
21583         }
21584
21585         data = me.getData(key, value);
21586         key = data[0];
21587         value = data[1];
21588         me.map[key] = value;
21589         ++me.length;
21590         me.fireEvent('add', me, key, value);
21591         return value;
21592     },
21593
21594     /**
21595      * Replaces an item in the hash. If the key doesn't exist, the
21596      * {@link #add} method will be used.
21597      * @param {String} key The key of the item.
21598      * @param {Object} value The new value for the item.
21599      * @return {Object} The new value of the item.
21600      */
21601     replace: function(key, value) {
21602         var me = this,
21603             map = me.map,
21604             old;
21605
21606         if (!me.containsKey(key)) {
21607             me.add(key, value);
21608         }
21609         old = map[key];
21610         map[key] = value;
21611         me.fireEvent('replace', me, key, value, old);
21612         return value;
21613     },
21614
21615     /**
21616      * Remove an item from the hash.
21617      * @param {Object} o The value of the item to remove.
21618      * @return {Boolean} True if the item was successfully removed.
21619      */
21620     remove: function(o) {
21621         var key = this.findKey(o);
21622         if (key !== undefined) {
21623             return this.removeAtKey(key);
21624         }
21625         return false;
21626     },
21627
21628     /**
21629      * Remove an item from the hash.
21630      * @param {String} key The key to remove.
21631      * @return {Boolean} True if the item was successfully removed.
21632      */
21633     removeAtKey: function(key) {
21634         var me = this,
21635             value;
21636
21637         if (me.containsKey(key)) {
21638             value = me.map[key];
21639             delete me.map[key];
21640             --me.length;
21641             me.fireEvent('remove', me, key, value);
21642             return true;
21643         }
21644         return false;
21645     },
21646
21647     /**
21648      * Retrieves an item with a particular key.
21649      * @param {String} key The key to lookup.
21650      * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
21651      */
21652     get: function(key) {
21653         return this.map[key];
21654     },
21655
21656     /**
21657      * Removes all items from the hash.
21658      * @return {Ext.util.HashMap} this
21659      */
21660     clear: function(/* private */ initial) {
21661         var me = this;
21662         me.map = {};
21663         me.length = 0;
21664         if (initial !== true) {
21665             me.fireEvent('clear', me);
21666         }
21667         return me;
21668     },
21669
21670     /**
21671      * Checks whether a key exists in the hash.
21672      * @param {String} key The key to check for.
21673      * @return {Boolean} True if they key exists in the hash.
21674      */
21675     containsKey: function(key) {
21676         return this.map[key] !== undefined;
21677     },
21678
21679     /**
21680      * Checks whether a value exists in the hash.
21681      * @param {Object} value The value to check for.
21682      * @return {Boolean} True if the value exists in the dictionary.
21683      */
21684     contains: function(value) {
21685         return this.containsKey(this.findKey(value));
21686     },
21687
21688     /**
21689      * Return all of the keys in the hash.
21690      * @return {Array} An array of keys.
21691      */
21692     getKeys: function() {
21693         return this.getArray(true);
21694     },
21695
21696     /**
21697      * Return all of the values in the hash.
21698      * @return {Array} An array of values.
21699      */
21700     getValues: function() {
21701         return this.getArray(false);
21702     },
21703
21704     /**
21705      * Gets either the keys/values in an array from the hash.
21706      * @private
21707      * @param {Boolean} isKey True to extract the keys, otherwise, the value
21708      * @return {Array} An array of either keys/values from the hash.
21709      */
21710     getArray: function(isKey) {
21711         var arr = [],
21712             key,
21713             map = this.map;
21714         for (key in map) {
21715             if (map.hasOwnProperty(key)) {
21716                 arr.push(isKey ? key: map[key]);
21717             }
21718         }
21719         return arr;
21720     },
21721
21722     /**
21723      * Executes the specified function once for each item in the hash.
21724      * Returning false from the function will cease iteration.
21725      *
21726      * The paramaters passed to the function are:
21727      * <div class="mdetail-params"><ul>
21728      * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
21729      * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
21730      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
21731      * </ul></div>
21732      * @param {Function} fn The function to execute.
21733      * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
21734      * @return {Ext.util.HashMap} this
21735      */
21736     each: function(fn, scope) {
21737         // copy items so they may be removed during iteration.
21738         var items = Ext.apply({}, this.map),
21739             key,
21740             length = this.length;
21741
21742         scope = scope || this;
21743         for (key in items) {
21744             if (items.hasOwnProperty(key)) {
21745                 if (fn.call(scope, key, items[key], length) === false) {
21746                     break;
21747                 }
21748             }
21749         }
21750         return this;
21751     },
21752
21753     /**
21754      * Performs a shallow copy on this hash.
21755      * @return {Ext.util.HashMap} The new hash object.
21756      */
21757     clone: function() {
21758         var hash = new this.self(),
21759             map = this.map,
21760             key;
21761
21762         hash.suspendEvents();
21763         for (key in map) {
21764             if (map.hasOwnProperty(key)) {
21765                 hash.add(key, map[key]);
21766             }
21767         }
21768         hash.resumeEvents();
21769         return hash;
21770     },
21771
21772     /**
21773      * @private
21774      * Find the key for a value.
21775      * @param {Object} value The value to find.
21776      * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
21777      */
21778     findKey: function(value) {
21779         var key,
21780             map = this.map;
21781
21782         for (key in map) {
21783             if (map.hasOwnProperty(key) && map[key] === value) {
21784                 return key;
21785             }
21786         }
21787         return undefined;
21788     }
21789 });
21790
21791 /**
21792  * @class Ext.Template
21793  * <p>Represents an HTML fragment template. Templates may be {@link #compile precompiled}
21794  * for greater performance.</p>
21795  * An instance of this class may be created by passing to the constructor either
21796  * a single argument, or multiple arguments:
21797  * <div class="mdetail-params"><ul>
21798  * <li><b>single argument</b> : String/Array
21799  * <div class="sub-desc">
21800  * The single argument may be either a String or an Array:<ul>
21801  * <li><tt>String</tt> : </li><pre><code>
21802 var t = new Ext.Template("&lt;div>Hello {0}.&lt;/div>");
21803 t.{@link #append}('some-element', ['foo']);
21804    </code></pre>
21805  * <li><tt>Array</tt> : </li>
21806  * An Array will be combined with <code>join('')</code>.
21807 <pre><code>
21808 var t = new Ext.Template([
21809     '&lt;div name="{id}"&gt;',
21810         '&lt;span class="{cls}"&gt;{name:trim} {value:ellipsis(10)}&lt;/span&gt;',
21811     '&lt;/div&gt;',
21812 ]);
21813 t.{@link #compile}();
21814 t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
21815    </code></pre>
21816  * </ul></div></li>
21817  * <li><b>multiple arguments</b> : String, Object, Array, ...
21818  * <div class="sub-desc">
21819  * Multiple arguments will be combined with <code>join('')</code>.
21820  * <pre><code>
21821 var t = new Ext.Template(
21822     '&lt;div name="{id}"&gt;',
21823         '&lt;span class="{cls}"&gt;{name} {value}&lt;/span&gt;',
21824     '&lt;/div&gt;',
21825     // a configuration object:
21826     {
21827         compiled: true,      // {@link #compile} immediately
21828     }
21829 );
21830    </code></pre>
21831  * <p><b>Notes</b>:</p>
21832  * <div class="mdetail-params"><ul>
21833  * <li>For a list of available format functions, see {@link Ext.util.Format}.</li>
21834  * <li><code>disableFormats</code> reduces <code>{@link #apply}</code> time
21835  * when no formatting is required.</li>
21836  * </ul></div>
21837  * </div></li>
21838  * </ul></div>
21839  * @param {Mixed} config
21840  */
21841
21842 Ext.define('Ext.Template', {
21843
21844     /* Begin Definitions */
21845
21846     requires: ['Ext.core.DomHelper', 'Ext.util.Format'],
21847
21848     statics: {
21849         /**
21850          * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
21851          * @param {String/HTMLElement} el A DOM element or its id
21852          * @param {Object} config A configuration object
21853          * @return {Ext.Template} The created template
21854          * @static
21855          */
21856         from: function(el, config) {
21857             el = Ext.getDom(el);
21858             return new this(el.value || el.innerHTML, config || '');
21859         }
21860     },
21861
21862     /* End Definitions */
21863
21864     constructor: function(html) {
21865         var me = this,
21866             args = arguments,
21867             buffer = [],
21868             i = 0,
21869             length = args.length,
21870             value;
21871
21872         me.initialConfig = {};
21873
21874         if (length > 1) {
21875             for (; i < length; i++) {
21876                 value = args[i];
21877                 if (typeof value == 'object') {
21878                     Ext.apply(me.initialConfig, value);
21879                     Ext.apply(me, value);
21880                 } else {
21881                     buffer.push(value);
21882                 }
21883             }
21884             html = buffer.join('');
21885         } else {
21886             if (Ext.isArray(html)) {
21887                 buffer.push(html.join(''));
21888             } else {
21889                 buffer.push(html);
21890             }
21891         }
21892
21893         // @private
21894         me.html = buffer.join('');
21895
21896         if (me.compiled) {
21897             me.compile();
21898         }
21899     },
21900     isTemplate: true,
21901     /**
21902      * @cfg {Boolean} disableFormats true to disable format functions in the template. If the template doesn't contain format functions, setting
21903      * disableFormats to true will reduce apply time (defaults to false)
21904      */
21905     disableFormats: false,
21906
21907     re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
21908     /**
21909      * Returns an HTML fragment of this template with the specified values applied.
21910      * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
21911      * @return {String} The HTML fragment
21912      * @hide repeat doc
21913      */
21914     applyTemplate: function(values) {
21915         var me = this,
21916             useFormat = me.disableFormats !== true,
21917             fm = Ext.util.Format,
21918             tpl = me;
21919
21920         if (me.compiled) {
21921             return me.compiled(values);
21922         }
21923         function fn(m, name, format, args) {
21924             if (format && useFormat) {
21925                 if (args) {
21926                     args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
21927                 } else {
21928                     args = [values[name]];
21929                 }
21930                 if (format.substr(0, 5) == "this.") {
21931                     return tpl[format.substr(5)].apply(tpl, args);
21932                 }
21933                 else {
21934                     return fm[format].apply(fm, args);
21935                 }
21936             }
21937             else {
21938                 return values[name] !== undefined ? values[name] : "";
21939             }
21940         }
21941         return me.html.replace(me.re, fn);
21942     },
21943
21944     /**
21945      * Sets the HTML used as the template and optionally compiles it.
21946      * @param {String} html
21947      * @param {Boolean} compile (optional) True to compile the template (defaults to undefined)
21948      * @return {Ext.Template} this
21949      */
21950     set: function(html, compile) {
21951         var me = this;
21952         me.html = html;
21953         me.compiled = null;
21954         return compile ? me.compile() : me;
21955     },
21956
21957     compileARe: /\\/g,
21958     compileBRe: /(\r\n|\n)/g,
21959     compileCRe: /'/g,
21960     /**
21961      * Compiles the template into an internal function, eliminating the RegEx overhead.
21962      * @return {Ext.Template} this
21963      * @hide repeat doc
21964      */
21965     compile: function() {
21966         var me = this,
21967             fm = Ext.util.Format,
21968             useFormat = me.disableFormats !== true,
21969             body, bodyReturn;
21970
21971         function fn(m, name, format, args) {
21972             if (format && useFormat) {
21973                 args = args ? ',' + args: "";
21974                 if (format.substr(0, 5) != "this.") {
21975                     format = "fm." + format + '(';
21976                 }
21977                 else {
21978                     format = 'this.' + format.substr(5) + '(';
21979                 }
21980             }
21981             else {
21982                 args = '';
21983                 format = "(values['" + name + "'] == undefined ? '' : ";
21984             }
21985             return "'," + format + "values['" + name + "']" + args + ") ,'";
21986         }
21987
21988         bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
21989         body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
21990         eval(body);
21991         return me;
21992     },
21993
21994     /**
21995      * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
21996      * @param {Mixed} el The context element
21997      * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
21998      * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
21999      * @return {HTMLElement/Ext.core.Element} The new node or Element
22000      */
22001     insertFirst: function(el, values, returnElement) {
22002         return this.doInsert('afterBegin', el, values, returnElement);
22003     },
22004
22005     /**
22006      * Applies the supplied values to the template and inserts the new node(s) before el.
22007      * @param {Mixed} el The context element
22008      * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
22009      * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
22010      * @return {HTMLElement/Ext.core.Element} The new node or Element
22011      */
22012     insertBefore: function(el, values, returnElement) {
22013         return this.doInsert('beforeBegin', el, values, returnElement);
22014     },
22015
22016     /**
22017      * Applies the supplied values to the template and inserts the new node(s) after el.
22018      * @param {Mixed} el The context element
22019      * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
22020      * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
22021      * @return {HTMLElement/Ext.core.Element} The new node or Element
22022      */
22023     insertAfter: function(el, values, returnElement) {
22024         return this.doInsert('afterEnd', el, values, returnElement);
22025     },
22026
22027     /**
22028      * Applies the supplied <code>values</code> to the template and appends
22029      * the new node(s) to the specified <code>el</code>.
22030      * <p>For example usage {@link #Template see the constructor}.</p>
22031      * @param {Mixed} el The context element
22032      * @param {Object/Array} values
22033      * The template values. Can be an array if the params are numeric (i.e. <code>{0}</code>)
22034      * or an object (i.e. <code>{foo: 'bar'}</code>).
22035      * @param {Boolean} returnElement (optional) true to return an Ext.core.Element (defaults to undefined)
22036      * @return {HTMLElement/Ext.core.Element} The new node or Element
22037      */
22038     append: function(el, values, returnElement) {
22039         return this.doInsert('beforeEnd', el, values, returnElement);
22040     },
22041
22042     doInsert: function(where, el, values, returnEl) {
22043         el = Ext.getDom(el);
22044         var newNode = Ext.core.DomHelper.insertHtml(where, el, this.applyTemplate(values));
22045         return returnEl ? Ext.get(newNode, true) : newNode;
22046     },
22047
22048     /**
22049      * Applies the supplied values to the template and overwrites the content of el with the new node(s).
22050      * @param {Mixed} el The context element
22051      * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
22052      * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
22053      * @return {HTMLElement/Ext.core.Element} The new node or Element
22054      */
22055     overwrite: function(el, values, returnElement) {
22056         el = Ext.getDom(el);
22057         el.innerHTML = this.applyTemplate(values);
22058         return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
22059     }
22060 }, function() {
22061
22062     /**
22063      * Alias for {@link #applyTemplate}
22064      * Returns an HTML fragment of this template with the specified <code>values</code> applied.
22065      * @param {Object/Array} values
22066      * The template values. Can be an array if the params are numeric (i.e. <code>{0}</code>)
22067      * or an object (i.e. <code>{foo: 'bar'}</code>).
22068      * @return {String} The HTML fragment
22069      * @member Ext.Template
22070      * @method apply
22071      */
22072     this.createAlias('apply', 'applyTemplate');
22073 });
22074
22075 /**
22076  * @class Ext.ComponentQuery
22077  * @extends Object
22078  *
22079  * Provides searching of Components within Ext.ComponentManager (globally) or a specific
22080  * Ext.container.Container on the document with a similar syntax to a CSS selector.
22081  *
22082  * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
22083 <ul>
22084     <li>component or .component</li>
22085     <li>gridpanel or .gridpanel</li>
22086 </ul>
22087  *
22088  * An itemId or id must be prefixed with a #
22089 <ul>
22090     <li>#myContainer</li>
22091 </ul>
22092  *
22093  *
22094  * Attributes must be wrapped in brackets
22095 <ul>
22096     <li>component[autoScroll]</li>
22097     <li>panel[title="Test"]</li>
22098 </ul>
22099  *
22100  * Member expressions from candidate Components may be tested. If the expression returns a <i>truthy</i> value,
22101  * the candidate Component will be included in the query:<pre><code>
22102 var disabledFields = myFormPanel.query("{isDisabled()}");
22103 </code></pre>
22104  *
22105  * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:<code><pre>
22106 // Function receives array and returns a filtered array.
22107 Ext.ComponentQuery.pseudos.invalid = function(items) {
22108     var i = 0, l = items.length, c, result = [];
22109     for (; i < l; i++) {
22110         if (!(c = items[i]).isValid()) {
22111             result.push(c);
22112         }
22113     }
22114     return result;
22115 };
22116
22117 var invalidFields = myFormPanel.query('field:invalid');
22118 if (invalidFields.length) {
22119     invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
22120     for (var i = 0, l = invalidFields.length; i < l; i++) {
22121         invalidFields[i].getEl().frame("red");
22122     }
22123 }
22124 </pre></code>
22125  * <p>
22126  * Default pseudos include:<br />
22127  * - not
22128  * </p>
22129  *
22130  * Queries return an array of components.
22131  * Here are some example queries.
22132 <pre><code>
22133     // retrieve all Ext.Panels in the document by xtype
22134     var panelsArray = Ext.ComponentQuery.query('panel');
22135
22136     // retrieve all Ext.Panels within the container with an id myCt
22137     var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
22138
22139     // retrieve all direct children which are Ext.Panels within myCt
22140     var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
22141
22142     // retrieve all gridpanels and listviews
22143     var gridsAndLists = Ext.ComponentQuery.query('gridpanel, listview');
22144 </code></pre>
22145
22146 For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
22147 {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
22148 {@link Ext.Component#up}.
22149  * @singleton
22150  */
22151 Ext.define('Ext.ComponentQuery', {
22152     singleton: true,
22153     uses: ['Ext.ComponentManager']
22154 }, function() {
22155
22156     var cq = this,
22157
22158         // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
22159         // as a member on each item in the passed array.
22160         filterFnPattern = [
22161             'var r = [],',
22162                 'i = 0,',
22163                 'it = items,',
22164                 'l = it.length,',
22165                 'c;',
22166             'for (; i < l; i++) {',
22167                 'c = it[i];',
22168                 'if (c.{0}) {',
22169                    'r.push(c);',
22170                 '}',
22171             '}',
22172             'return r;'
22173         ].join(''),
22174
22175         filterItems = function(items, operation) {
22176             // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
22177             // The operation's method loops over each item in the candidate array and
22178             // returns an array of items which match its criteria
22179             return operation.method.apply(this, [ items ].concat(operation.args));
22180         },
22181
22182         getItems = function(items, mode) {
22183             var result = [],
22184                 i = 0,
22185                 length = items.length,
22186                 candidate,
22187                 deep = mode !== '>';
22188                 
22189             for (; i < length; i++) {
22190                 candidate = items[i];
22191                 if (candidate.getRefItems) {
22192                     result = result.concat(candidate.getRefItems(deep));
22193                 }
22194             }
22195             return result;
22196         },
22197
22198         getAncestors = function(items) {
22199             var result = [],
22200                 i = 0,
22201                 length = items.length,
22202                 candidate;
22203             for (; i < length; i++) {
22204                 candidate = items[i];
22205                 while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
22206                     result.push(candidate);
22207                 }
22208             }
22209             return result;
22210         },
22211
22212         // Filters the passed candidate array and returns only items which match the passed xtype
22213         filterByXType = function(items, xtype, shallow) {
22214             if (xtype === '*') {
22215                 return items.slice();
22216             }
22217             else {
22218                 var result = [],
22219                     i = 0,
22220                     length = items.length,
22221                     candidate;
22222                 for (; i < length; i++) {
22223                     candidate = items[i];
22224                     if (candidate.isXType(xtype, shallow)) {
22225                         result.push(candidate);
22226                     }
22227                 }
22228                 return result;
22229             }
22230         },
22231
22232         // Filters the passed candidate array and returns only items which have the passed className
22233         filterByClassName = function(items, className) {
22234             var EA = Ext.Array,
22235                 result = [],
22236                 i = 0,
22237                 length = items.length,
22238                 candidate;
22239             for (; i < length; i++) {
22240                 candidate = items[i];
22241                 if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
22242                     result.push(candidate);
22243                 }
22244             }
22245             return result;
22246         },
22247
22248         // Filters the passed candidate array and returns only items which have the specified property match
22249         filterByAttribute = function(items, property, operator, value) {
22250             var result = [],
22251                 i = 0,
22252                 length = items.length,
22253                 candidate;
22254             for (; i < length; i++) {
22255                 candidate = items[i];
22256                 if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
22257                     result.push(candidate);
22258                 }
22259             }
22260             return result;
22261         },
22262
22263         // Filters the passed candidate array and returns only items which have the specified itemId or id
22264         filterById = function(items, id) {
22265             var result = [],
22266                 i = 0,
22267                 length = items.length,
22268                 candidate;
22269             for (; i < length; i++) {
22270                 candidate = items[i];
22271                 if (candidate.getItemId() === id) {
22272                     result.push(candidate);
22273                 }
22274             }
22275             return result;
22276         },
22277
22278         // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
22279         filterByPseudo = function(items, name, value) {
22280             return cq.pseudos[name](items, value);
22281         },
22282
22283         // Determines leading mode
22284         // > for direct child, and ^ to switch to ownerCt axis
22285         modeRe = /^(\s?([>\^])\s?|\s|$)/,
22286
22287         // Matches a token with possibly (true|false) appended for the "shallow" parameter
22288         tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
22289
22290         matchers = [{
22291             // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
22292             re: /^\.([\w\-]+)(?:\((true|false)\))?/,
22293             method: filterByXType
22294         },{
22295             // checks for [attribute=value]
22296             re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
22297             method: filterByAttribute
22298         }, {
22299             // checks for #cmpItemId
22300             re: /^#([\w\-]+)/,
22301             method: filterById
22302         }, {
22303             // checks for :<pseudo_class>(<selector>)
22304             re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
22305             method: filterByPseudo
22306         }, {
22307             // checks for {<member_expression>}
22308             re: /^(?:\{([^\}]+)\})/,
22309             method: filterFnPattern
22310         }];
22311
22312     /**
22313      * @class Ext.ComponentQuery.Query
22314      * @extends Object
22315      * @private
22316      */
22317     cq.Query = Ext.extend(Object, {
22318         constructor: function(cfg) {
22319             cfg = cfg || {};
22320             Ext.apply(this, cfg);
22321         },
22322
22323         /**
22324          * @private
22325          * Executes this Query upon the selected root.
22326          * The root provides the initial source of candidate Component matches which are progressively
22327          * filtered by iterating through this Query's operations cache.
22328          * If no root is provided, all registered Components are searched via the ComponentManager.
22329          * root may be a Container who's descendant Components are filtered
22330          * root may be a Component with an implementation of getRefItems which provides some nested Components such as the
22331          * docked items within a Panel.
22332          * root may be an array of candidate Components to filter using this Query.
22333          */
22334         execute : function(root) {
22335             var operations = this.operations,
22336                 i = 0,
22337                 length = operations.length,
22338                 operation,
22339                 workingItems;
22340
22341             // no root, use all Components in the document
22342             if (!root) {
22343                 workingItems = Ext.ComponentManager.all.getArray();
22344             }
22345             // Root is a candidate Array
22346             else if (Ext.isArray(root)) {
22347                 workingItems = root;
22348             }
22349
22350             // We are going to loop over our operations and take care of them
22351             // one by one.
22352             for (; i < length; i++) {
22353                 operation = operations[i];
22354
22355                 // The mode operation requires some custom handling.
22356                 // All other operations essentially filter down our current
22357                 // working items, while mode replaces our current working
22358                 // items by getting children from each one of our current
22359                 // working items. The type of mode determines the type of
22360                 // children we get. (e.g. > only gets direct children)
22361                 if (operation.mode === '^') {
22362                     workingItems = getAncestors(workingItems || [root]);
22363                 }
22364                 else if (operation.mode) {
22365                     workingItems = getItems(workingItems || [root], operation.mode);
22366                 }
22367                 else {
22368                     workingItems = filterItems(workingItems || getItems([root]), operation);
22369                 }
22370
22371                 // If this is the last operation, it means our current working
22372                 // items are the final matched items. Thus return them!
22373                 if (i === length -1) {
22374                     return workingItems;
22375                 }
22376             }
22377             return [];
22378         },
22379
22380         is: function(component) {
22381             var operations = this.operations,
22382                 components = Ext.isArray(component) ? component : [component],
22383                 originalLength = components.length,
22384                 lastOperation = operations[operations.length-1],
22385                 ln, i;
22386
22387             components = filterItems(components, lastOperation);
22388             if (components.length === originalLength) {
22389                 if (operations.length > 1) {
22390                     for (i = 0, ln = components.length; i < ln; i++) {
22391                         if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
22392                             return false;
22393                         }
22394                     }
22395                 }
22396                 return true;
22397             }
22398             return false;
22399         }
22400     });
22401
22402     Ext.apply(this, {
22403
22404         // private cache of selectors and matching ComponentQuery.Query objects
22405         cache: {},
22406
22407         // private cache of pseudo class filter functions
22408         pseudos: {
22409             not: function(components, selector){
22410                 var CQ = Ext.ComponentQuery,
22411                     i = 0,
22412                     length = components.length,
22413                     results = [],
22414                     index = -1,
22415                     component;
22416                 
22417                 for(; i < length; ++i) {
22418                     component = components[i];
22419                     if (!CQ.is(component, selector)) {
22420                         results[++index] = component;
22421                     }
22422                 }
22423                 return results;
22424             }
22425         },
22426
22427         /**
22428          * <p>Returns an array of matched Components from within the passed root object.</p>
22429          * <p>This method filters returned Components in a similar way to how CSS selector based DOM
22430          * queries work using a textual selector string.</p>
22431          * <p>See class summary for details.</p>
22432          * @param selector The selector string to filter returned Components
22433          * @param root <p>The Container within which to perform the query. If omitted, all Components
22434          * within the document are included in the search.</p>
22435          * <p>This parameter may also be an array of Components to filter according to the selector.</p>
22436          * @returns {Array} The matched Components.
22437          * @member Ext.ComponentQuery
22438          */
22439         query: function(selector, root) {
22440             var selectors = selector.split(','),
22441                 length = selectors.length,
22442                 i = 0,
22443                 results = [],
22444                 noDupResults = [], 
22445                 dupMatcher = {}, 
22446                 query, resultsLn, cmp;
22447
22448             for (; i < length; i++) {
22449                 selector = Ext.String.trim(selectors[i]);
22450                 query = this.cache[selector];
22451                 if (!query) {
22452                     this.cache[selector] = query = this.parse(selector);
22453                 }
22454                 results = results.concat(query.execute(root));
22455             }
22456
22457             // multiple selectors, potential to find duplicates
22458             // lets filter them out.
22459             if (length > 1) {
22460                 resultsLn = results.length;
22461                 for (i = 0; i < resultsLn; i++) {
22462                     cmp = results[i];
22463                     if (!dupMatcher[cmp.id]) {
22464                         noDupResults.push(cmp);
22465                         dupMatcher[cmp.id] = true;
22466                     }
22467                 }
22468                 results = noDupResults;
22469             }
22470             return results;
22471         },
22472
22473         /**
22474          * Tests whether the passed Component matches the selector string.
22475          * @param component The Component to test
22476          * @param selector The selector string to test against.
22477          * @return {Boolean} True if the Component matches the selector.
22478          * @member Ext.ComponentQuery
22479          */
22480         is: function(component, selector) {
22481             if (!selector) {
22482                 return true;
22483             }
22484             var query = this.cache[selector];
22485             if (!query) {
22486                 this.cache[selector] = query = this.parse(selector);
22487             }
22488             return query.is(component);
22489         },
22490
22491         parse: function(selector) {
22492             var operations = [],
22493                 length = matchers.length,
22494                 lastSelector,
22495                 tokenMatch,
22496                 matchedChar,
22497                 modeMatch,
22498                 selectorMatch,
22499                 i, matcher, method;
22500
22501             // We are going to parse the beginning of the selector over and
22502             // over again, slicing off the selector any portions we converted into an
22503             // operation, until it is an empty string.
22504             while (selector && lastSelector !== selector) {
22505                 lastSelector = selector;
22506
22507                 // First we check if we are dealing with a token like #, * or an xtype
22508                 tokenMatch = selector.match(tokenRe);
22509
22510                 if (tokenMatch) {
22511                     matchedChar = tokenMatch[1];
22512
22513                     // If the token is prefixed with a # we push a filterById operation to our stack
22514                     if (matchedChar === '#') {
22515                         operations.push({
22516                             method: filterById,
22517                             args: [Ext.String.trim(tokenMatch[2])]
22518                         });
22519                     }
22520                     // If the token is prefixed with a . we push a filterByClassName operation to our stack
22521                     // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
22522                     else if (matchedChar === '.') {
22523                         operations.push({
22524                             method: filterByClassName,
22525                             args: [Ext.String.trim(tokenMatch[2])]
22526                         });
22527                     }
22528                     // If the token is a * or an xtype string, we push a filterByXType
22529                     // operation to the stack.
22530                     else {
22531                         operations.push({
22532                             method: filterByXType,
22533                             args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
22534                         });
22535                     }
22536
22537                     // Now we slice of the part we just converted into an operation
22538                     selector = selector.replace(tokenMatch[0], '');
22539                 }
22540
22541                 // If the next part of the query is not a space or > or ^, it means we
22542                 // are going to check for more things that our current selection
22543                 // has to comply to.
22544                 while (!(modeMatch = selector.match(modeRe))) {
22545                     // Lets loop over each type of matcher and execute it
22546                     // on our current selector.
22547                     for (i = 0; selector && i < length; i++) {
22548                         matcher = matchers[i];
22549                         selectorMatch = selector.match(matcher.re);
22550                         method = matcher.method;
22551
22552                         // If we have a match, add an operation with the method
22553                         // associated with this matcher, and pass the regular
22554                         // expression matches are arguments to the operation.
22555                         if (selectorMatch) {
22556                             operations.push({
22557                                 method: Ext.isString(matcher.method)
22558                                     // Turn a string method into a function by formatting the string with our selector matche expression
22559                                     // A new method is created for different match expressions, eg {id=='textfield-1024'}
22560                                     // Every expression may be different in different selectors.
22561                                     ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
22562                                     : matcher.method,
22563                                 args: selectorMatch.slice(1)
22564                             });
22565                             selector = selector.replace(selectorMatch[0], '');
22566                             break; // Break on match
22567                         }
22568                         // Exhausted all matches: It's an error
22569                         if (i === (length - 1)) {
22570                             Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
22571                         }
22572                     }
22573                 }
22574
22575                 // Now we are going to check for a mode change. This means a space
22576                 // or a > to determine if we are going to select all the children
22577                 // of the currently matched items, or a ^ if we are going to use the
22578                 // ownerCt axis as the candidate source.
22579                 if (modeMatch[1]) { // Assignment, and test for truthiness!
22580                     operations.push({
22581                         mode: modeMatch[2]||modeMatch[1]
22582                     });
22583                     selector = selector.replace(modeMatch[0], '');
22584                 }
22585             }
22586
22587             //  Now that we have all our operations in an array, we are going
22588             // to create a new Query using these operations.
22589             return new cq.Query({
22590                 operations: operations
22591             });
22592         }
22593     });
22594 });
22595 /**
22596  * @class Ext.util.Filter
22597  * @extends Object
22598  * <p>Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
22599  * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the context
22600  * of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching on their
22601  * records. Example usage:</p>
22602 <pre><code>
22603 //set up a fictional MixedCollection containing a few people to filter on
22604 var allNames = new Ext.util.MixedCollection();
22605 allNames.addAll([
22606     {id: 1, name: 'Ed',    age: 25},
22607     {id: 2, name: 'Jamie', age: 37},
22608     {id: 3, name: 'Abe',   age: 32},
22609     {id: 4, name: 'Aaron', age: 26},
22610     {id: 5, name: 'David', age: 32}
22611 ]);
22612
22613 var ageFilter = new Ext.util.Filter({
22614     property: 'age',
22615     value   : 32
22616 });
22617
22618 var longNameFilter = new Ext.util.Filter({
22619     filterFn: function(item) {
22620         return item.name.length > 4;
22621     }
22622 });
22623
22624 //a new MixedCollection with the 3 names longer than 4 characters
22625 var longNames = allNames.filter(longNameFilter);
22626
22627 //a new MixedCollection with the 2 people of age 24:
22628 var youngFolk = allNames.filter(ageFilter);
22629 </code></pre>
22630  * @constructor
22631  * @param {Object} config Config object
22632  */
22633 Ext.define('Ext.util.Filter', {
22634
22635     /* Begin Definitions */
22636
22637     /* End Definitions */
22638     /**
22639      * @cfg {String} property The property to filter on. Required unless a {@link #filter} is passed
22640      */
22641     
22642     /**
22643      * @cfg {Function} filterFn A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} 
22644      * in turn. Should return true to accept each item or false to reject it
22645      */
22646     
22647     /**
22648      * @cfg {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
22649      */
22650     anyMatch: false,
22651     
22652     /**
22653      * @cfg {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false.
22654      * Ignored if anyMatch is true.
22655      */
22656     exactMatch: false,
22657     
22658     /**
22659      * @cfg {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
22660      */
22661     caseSensitive: false,
22662     
22663     /**
22664      * @cfg {String} root Optional root property. This is mostly useful when filtering a Store, in which case we set the
22665      * root to 'data' to make the filter pull the {@link #property} out of the data object of each item
22666      */
22667     
22668     constructor: function(config) {
22669         Ext.apply(this, config);
22670         
22671         //we're aliasing filter to filterFn mostly for API cleanliness reasons, despite the fact it dirties the code here.
22672         //Ext.util.Sorter takes a sorterFn property but allows .sort to be called - we do the same here
22673         this.filter = this.filter || this.filterFn;
22674         
22675         if (this.filter == undefined) {
22676             if (this.property == undefined || this.value == undefined) {
22677                 // Commented this out temporarily because it stops us using string ids in models. TODO: Remove this once
22678                 // Model has been updated to allow string ids
22679                 
22680                 // Ext.Error.raise("A Filter requires either a property or a filterFn to be set");
22681             } else {
22682                 this.filter = this.createFilterFn();
22683             }
22684             
22685             this.filterFn = this.filter;
22686         }
22687     },
22688     
22689     /**
22690      * @private
22691      * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
22692      */
22693     createFilterFn: function() {
22694         var me       = this,
22695             matcher  = me.createValueMatcher(),
22696             property = me.property;
22697         
22698         return function(item) {
22699             return matcher.test(me.getRoot.call(me, item)[property]);
22700         };
22701     },
22702     
22703     /**
22704      * @private
22705      * Returns the root property of the given item, based on the configured {@link #root} property
22706      * @param {Object} item The item
22707      * @return {Object} The root property of the object
22708      */
22709     getRoot: function(item) {
22710         return this.root == undefined ? item : item[this.root];
22711     },
22712     
22713     /**
22714      * @private
22715      * Returns a regular expression based on the given value and matching options
22716      */
22717     createValueMatcher : function() {
22718         var me            = this,
22719             value         = me.value,
22720             anyMatch      = me.anyMatch,
22721             exactMatch    = me.exactMatch,
22722             caseSensitive = me.caseSensitive,
22723             escapeRe      = Ext.String.escapeRegex;
22724         
22725         if (!value.exec) { // not a regex
22726             value = String(value);
22727
22728             if (anyMatch === true) {
22729                 value = escapeRe(value);
22730             } else {
22731                 value = '^' + escapeRe(value);
22732                 if (exactMatch === true) {
22733                     value += '$';
22734                 }
22735             }
22736             value = new RegExp(value, caseSensitive ? '' : 'i');
22737          }
22738          
22739          return value;
22740     }
22741 });
22742 /**
22743  * @class Ext.util.Sorter
22744  * @extends Object
22745  * Represents a single sorter that can be applied to a Store
22746  */
22747 Ext.define('Ext.util.Sorter', {
22748
22749     /**
22750      * @cfg {String} property The property to sort by. Required unless {@link #sorter} is provided
22751      */
22752     
22753     /**
22754      * @cfg {Function} sorterFn A specific sorter function to execute. Can be passed instead of {@link #property}
22755      */
22756     
22757     /**
22758      * @cfg {String} root Optional root property. This is mostly useful when sorting a Store, in which case we set the
22759      * root to 'data' to make the filter pull the {@link #property} out of the data object of each item
22760      */
22761     
22762     /**
22763      * @cfg {Function} transform A function that will be run on each value before
22764      * it is compared in the sorter. The function will receive a single argument,
22765      * the value.
22766      */
22767     
22768     /**
22769      * @cfg {String} direction The direction to sort by. Defaults to ASC
22770      */
22771     direction: "ASC",
22772     
22773     constructor: function(config) {
22774         var me = this;
22775         
22776         Ext.apply(me, config);
22777         
22778         if (me.property == undefined && me.sorterFn == undefined) {
22779             Ext.Error.raise("A Sorter requires either a property or a sorter function");
22780         }
22781         
22782         me.updateSortFunction();
22783     },
22784     
22785     /**
22786      * @private
22787      * Creates and returns a function which sorts an array by the given property and direction
22788      * @return {Function} A function which sorts by the property/direction combination provided
22789      */
22790     createSortFunction: function(sorterFn) {
22791         var me        = this,
22792             property  = me.property,
22793             direction = me.direction || "ASC",
22794             modifier  = direction.toUpperCase() == "DESC" ? -1 : 1;
22795         
22796         //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
22797         //-1 if object 2 is greater or 0 if they are equal
22798         return function(o1, o2) {
22799             return modifier * sorterFn.call(me, o1, o2);
22800         };
22801     },
22802     
22803     /**
22804      * @private
22805      * Basic default sorter function that just compares the defined property of each object
22806      */
22807     defaultSorterFn: function(o1, o2) {
22808         var me = this,
22809             transform = me.transform,
22810             v1 = me.getRoot(o1)[me.property],
22811             v2 = me.getRoot(o2)[me.property];
22812             
22813         if (transform) {
22814             v1 = transform(v1);
22815             v2 = transform(v2);
22816         }
22817
22818         return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
22819     },
22820     
22821     /**
22822      * @private
22823      * Returns the root property of the given item, based on the configured {@link #root} property
22824      * @param {Object} item The item
22825      * @return {Object} The root property of the object
22826      */
22827     getRoot: function(item) {
22828         return this.root == undefined ? item : item[this.root];
22829     },
22830     
22831     // @TODO: Add docs for these three methods
22832     setDirection: function(direction) {
22833         var me = this;
22834         me.direction = direction;
22835         me.updateSortFunction();
22836     },
22837     
22838     toggle: function() {
22839         var me = this;
22840         me.direction = Ext.String.toggle(me.direction, "ASC", "DESC");
22841         me.updateSortFunction();
22842     },
22843     
22844     updateSortFunction: function() {
22845         var me = this;
22846         me.sort = me.createSortFunction(me.sorterFn || me.defaultSorterFn);
22847     }
22848 });
22849 /**
22850  * @class Ext.ElementLoader
22851  * A class used to load remote content to an Element. Sample usage:
22852  * <pre><code>
22853 Ext.get('el').load({
22854     url: 'myPage.php',
22855     scripts: true,
22856     params: {
22857         id: 1
22858     }
22859 });
22860  * </code></pre>
22861  * <p>
22862  * In general this class will not be instanced directly, rather the {@link Ext.core.Element#load} method
22863  * will be used.
22864  * </p>
22865  */
22866 Ext.define('Ext.ElementLoader', {
22867
22868     /* Begin Definitions */
22869
22870     mixins: {
22871         observable: 'Ext.util.Observable'
22872     },
22873
22874     uses: [
22875         'Ext.data.Connection',
22876         'Ext.Ajax'
22877     ],
22878     
22879     statics: {
22880         Renderer: {
22881             Html: function(loader, response, active){
22882                 loader.getTarget().update(response.responseText, active.scripts === true);
22883                 return true;
22884             }
22885         }     
22886     },
22887
22888     /* End Definitions */
22889
22890     /**
22891      * @cfg {String} url The url to retrieve the content from. Defaults to <tt>null</tt>.
22892      */
22893     url: null,
22894
22895     /**
22896      * @cfg {Object} params Any params to be attached to the Ajax request. These parameters will
22897      * be overridden by any params in the load options. Defaults to <tt>null</tt>.
22898      */
22899     params: null,
22900
22901     /**
22902      * @cfg {Object} baseParams Params that will be attached to every request. These parameters
22903      * will not be overridden by any params in the load options. Defaults to <tt>null</tt>.
22904      */
22905     baseParams: null,
22906
22907     /**
22908      * @cfg {Boolean/Object} autoLoad True to have the loader make a request as soon as it is created. Defaults to <tt>false</tt>.
22909      * This argument can also be a set of options that will be passed to {@link #load} is called.
22910      */
22911     autoLoad: false,
22912
22913     /**
22914      * @cfg {Mixed} target The target element for the loader. It can be the DOM element, the id or an Ext.Element.
22915      */
22916     target: null,
22917
22918     /**
22919      * @cfg {Mixed} loadMask True or a string to show when the element is loading.
22920      */
22921     loadMask: false,
22922
22923     /**
22924      * @cfg {Object} ajaxOptions Any additional options to be passed to the request, for example timeout or headers. Defaults to <tt>null</tt>.
22925      */
22926     ajaxOptions: null,
22927     
22928     /**
22929      * @cfg {Boolean} scripts True to parse any inline script tags in the response.
22930      */
22931     scripts: false,
22932
22933     /**
22934      * @cfg {Function} success A function to be called when a load request is successful.
22935      */
22936
22937     /**
22938      * @cfg {Function} failure A function to be called when a load request fails.
22939      */
22940
22941     /**
22942      * @cfg {Object} scope The scope to execute the {@link #success} and {@link #failure} functions in.
22943      */
22944     
22945     /**
22946      * @cfg {Function} renderer A custom function to render the content to the element. The passed parameters
22947      * are
22948      * <ul>
22949      * <li>The loader</li>
22950      * <li>The response</li>
22951      * <li>The active request</li>
22952      * </ul>
22953      */
22954
22955     isLoader: true,
22956
22957     constructor: function(config) {
22958         var me = this,
22959             autoLoad;
22960         
22961         config = config || {};
22962         Ext.apply(me, config);
22963         me.setTarget(me.target);
22964         me.addEvents(
22965             /**
22966              * @event beforeload
22967              * Fires before a load request is made to the server.
22968              * Returning false from an event listener can prevent the load
22969              * from occurring.
22970              * @param {Ext.ElementLoader} this
22971              * @param {Object} options The options passed to the request
22972              */
22973             'beforeload',
22974
22975             /**
22976              * @event exception
22977              * Fires after an unsuccessful load.
22978              * @param {Ext.ElementLoader} this
22979              * @param {Object} response The response from the server
22980              * @param {Object} options The options passed to the request
22981              */
22982             'exception',
22983
22984             /**
22985              * @event exception
22986              * Fires after a successful load.
22987              * @param {Ext.ElementLoader} this
22988              * @param {Object} response The response from the server
22989              * @param {Object} options The options passed to the request
22990              */
22991             'load'
22992         );
22993
22994         // don't pass config because we have already applied it.
22995         me.mixins.observable.constructor.call(me);
22996
22997         if (me.autoLoad) {
22998             autoLoad = me.autoLoad;
22999             if (autoLoad === true) {
23000                 autoLoad = {};
23001             }
23002             me.load(autoLoad);
23003         }
23004     },
23005
23006     /**
23007      * Set an {Ext.Element} as the target of this loader. Note that if the target is changed,
23008      * any active requests will be aborted.
23009      * @param {Mixed} target The element
23010      */
23011     setTarget: function(target){
23012         var me = this;
23013         target = Ext.get(target);
23014         if (me.target && me.target != target) {
23015             me.abort();
23016         }
23017         me.target = target;
23018     },
23019
23020     /**
23021      * Get the target of this loader.
23022      * @return {Ext.Component} target The target, null if none exists.
23023      */
23024     getTarget: function(){
23025         return this.target || null;
23026     },
23027
23028     /**
23029      * Aborts the active load request
23030      */
23031     abort: function(){
23032         var active = this.active;
23033         if (active !== undefined) {
23034             Ext.Ajax.abort(active.request);
23035             if (active.mask) {
23036                 this.removeMask();
23037             }
23038             delete this.active;
23039         }
23040     },
23041     
23042     /**
23043      * Remove the mask on the target
23044      * @private
23045      */
23046     removeMask: function(){
23047         this.target.unmask();
23048     },
23049     
23050     /**
23051      * Add the mask on the target
23052      * @private
23053      * @param {Mixed} mask The mask configuration
23054      */
23055     addMask: function(mask){
23056         this.target.mask(mask === true ? null : mask);
23057     },
23058
23059     /**
23060      * Load new data from the server.
23061      * @param {Object} options The options for the request. They can be any configuration option that can be specified for
23062      * the class, with the exception of the target option. Note that any options passed to the method will override any
23063      * class defaults.
23064      */
23065     load: function(options) {
23066         if (!this.target) {
23067             Ext.Error.raise('A valid target is required when loading content');
23068         }
23069
23070         options = Ext.apply({}, options);
23071
23072         var me = this,
23073             target = me.target,
23074             mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
23075             params = Ext.apply({}, options.params),
23076             ajaxOptions = Ext.apply({}, options.ajaxOptions),
23077             callback = options.callback || me.callback,
23078             scope = options.scope || me.scope || me,
23079             request;
23080
23081         Ext.applyIf(ajaxOptions, me.ajaxOptions);
23082         Ext.applyIf(options, ajaxOptions);
23083
23084         Ext.applyIf(params, me.params);
23085         Ext.apply(params, me.baseParams);
23086
23087         Ext.applyIf(options, {
23088             url: me.url
23089         });
23090
23091         if (!options.url) {
23092             Ext.Error.raise('You must specify the URL from which content should be loaded');
23093         }
23094
23095         Ext.apply(options, {
23096             scope: me,
23097             params: params,
23098             callback: me.onComplete
23099         });
23100
23101         if (me.fireEvent('beforeload', me, options) === false) {
23102             return;
23103         }
23104
23105         if (mask) {
23106             me.addMask(mask);
23107         }
23108
23109         request = Ext.Ajax.request(options);
23110         me.active = {
23111             request: request,
23112             options: options,
23113             mask: mask,
23114             scope: scope,
23115             callback: callback,
23116             success: options.success || me.success,
23117             failure: options.failure || me.failure,
23118             renderer: options.renderer || me.renderer,
23119             scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
23120         };
23121         me.setOptions(me.active, options);
23122     },
23123     
23124     /**
23125      * Set any additional options on the active request
23126      * @private
23127      * @param {Object} active The active request
23128      * @param {Object} options The initial options
23129      */
23130     setOptions: Ext.emptyFn,
23131
23132     /**
23133      * Parse the response after the request completes
23134      * @private
23135      * @param {Object} options Ajax options
23136      * @param {Boolean} success Success status of the request
23137      * @param {Object} response The response object
23138      */
23139     onComplete: function(options, success, response) {
23140         var me = this,
23141             active = me.active,
23142             scope = active.scope,
23143             renderer = me.getRenderer(active.renderer);
23144
23145
23146         if (success) {
23147             success = renderer.call(me, me, response, active);
23148         }
23149
23150         if (success) {
23151             Ext.callback(active.success, scope, [me, response, options]);
23152             me.fireEvent('load', me, response, options);
23153         } else {
23154             Ext.callback(active.failure, scope, [me, response, options]);
23155             me.fireEvent('exception', me, response, options);
23156         }
23157         Ext.callback(active.callback, scope, [me, success, response, options]);
23158
23159         if (active.mask) {
23160             me.removeMask();
23161         }
23162
23163         delete me.active;
23164     },
23165
23166     /**
23167      * Gets the renderer to use
23168      * @private
23169      * @param {String/Function} renderer The renderer to use
23170      * @return {Function} A rendering function to use.
23171      */
23172     getRenderer: function(renderer){
23173         if (Ext.isFunction(renderer)) {
23174             return renderer;
23175         }
23176         return this.statics().Renderer.Html;
23177     },
23178     
23179     /**
23180      * Automatically refreshes the content over a specified period.
23181      * @param {Number} interval The interval to refresh in ms.
23182      * @param {Object} options (optional) The options to pass to the load method. See {@link #load}
23183      */
23184     startAutoRefresh: function(interval, options){
23185         var me = this;
23186         me.stopAutoRefresh();
23187         me.autoRefresh = setInterval(function(){
23188             me.load(options);
23189         }, interval);
23190     },
23191     
23192     /**
23193      * Clears any auto refresh. See {@link #startAutoRefresh}.
23194      */
23195     stopAutoRefresh: function(){
23196         clearInterval(this.autoRefresh);
23197         delete this.autoRefresh;
23198     },
23199     
23200     /**
23201      * Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
23202      * @return {Boolean} True if the loader is automatically refreshing
23203      */
23204     isAutoRefreshing: function(){
23205         return Ext.isDefined(this.autoRefresh);
23206     },
23207
23208     /**
23209      * Destroys the loader. Any active requests will be aborted.
23210      */
23211     destroy: function(){
23212         var me = this;
23213         me.stopAutoRefresh();
23214         delete me.target;
23215         me.abort();
23216         me.clearListeners();
23217     }
23218 });
23219
23220 /**
23221  * @class Ext.layout.Layout
23222  * @extends Object
23223  * @private
23224  * Base Layout class - extended by ComponentLayout and ContainerLayout
23225  */
23226
23227 Ext.define('Ext.layout.Layout', {
23228
23229     /* Begin Definitions */
23230
23231     /* End Definitions */
23232
23233     isLayout: true,
23234     initialized: false,
23235
23236     statics: {
23237         create: function(layout, defaultType) {
23238             var type;
23239             if (layout instanceof Ext.layout.Layout) {
23240                 return Ext.createByAlias('layout.' + layout);
23241             } else {
23242                 if (Ext.isObject(layout)) {
23243                     type = layout.type;
23244                 }
23245                 else {
23246                     type = layout || defaultType;
23247                     layout = {};
23248                 }
23249                 return Ext.createByAlias('layout.' + type, layout || {});
23250             }
23251         }
23252     },
23253
23254     constructor : function(config) {
23255         this.id = Ext.id(null, this.type + '-');
23256         Ext.apply(this, config);
23257     },
23258
23259     /**
23260      * @private
23261      */
23262     layout : function() {
23263         var me = this;
23264         me.layoutBusy = true;
23265         me.initLayout();
23266
23267         if (me.beforeLayout.apply(me, arguments) !== false) {
23268             me.layoutCancelled = false;
23269             me.onLayout.apply(me, arguments);
23270             me.childrenChanged = false;
23271             me.owner.needsLayout = false;
23272             me.layoutBusy = false;
23273             me.afterLayout.apply(me, arguments);
23274         }
23275         else {
23276             me.layoutCancelled = true;
23277         }
23278         me.layoutBusy = false;
23279         me.doOwnerCtLayouts();
23280     },
23281
23282     beforeLayout : function() {
23283         this.renderItems(this.getLayoutItems(), this.getRenderTarget());
23284         return true;
23285     },
23286
23287     /**
23288      * @private
23289      * Iterates over all passed items, ensuring they are rendered.  If the items are already rendered,
23290      * also determines if the items are in the proper place dom.
23291      */
23292     renderItems : function(items, target) {
23293         var ln = items.length,
23294             i = 0,
23295             item;
23296
23297         for (; i < ln; i++) {
23298             item = items[i];
23299             if (item && !item.rendered) {
23300                 this.renderItem(item, target, i);
23301             }
23302             else if (!this.isValidParent(item, target, i)) {
23303                 this.moveItem(item, target, i);
23304             }
23305         }
23306     },
23307
23308     // @private - Validates item is in the proper place in the dom.
23309     isValidParent : function(item, target, position) {
23310         var dom = item.el ? item.el.dom : Ext.getDom(item);
23311         if (dom && target && target.dom) {
23312             if (Ext.isNumber(position) && dom !== target.dom.childNodes[position]) {
23313                 return false;
23314             }
23315             return (dom.parentNode == (target.dom || target));
23316         }
23317         return false;
23318     },
23319
23320     /**
23321      * @private
23322      * Renders the given Component into the target Element.
23323      * @param {Ext.Component} item The Component to render
23324      * @param {Ext.core.Element} target The target Element
23325      * @param {Number} position The position within the target to render the item to
23326      */
23327     renderItem : function(item, target, position) {
23328         if (!item.rendered) {
23329             item.render(target, position);
23330             this.configureItem(item);
23331             this.childrenChanged = true;
23332         }
23333     },
23334
23335     /**
23336      * @private
23337      * Moved Component to the provided target instead.
23338      */
23339     moveItem : function(item, target, position) {
23340         // Make sure target is a dom element
23341         target = target.dom || target;
23342         if (typeof position == 'number') {
23343             position = target.childNodes[position];
23344         }
23345         target.insertBefore(item.el.dom, position || null);
23346         item.container = Ext.get(target);
23347         this.configureItem(item);
23348         this.childrenChanged = true;
23349     },
23350
23351     /**
23352      * @private
23353      * Adds the layout's targetCls if necessary and sets
23354      * initialized flag when complete.
23355      */
23356     initLayout : function() {
23357         if (!this.initialized && !Ext.isEmpty(this.targetCls)) {
23358             this.getTarget().addCls(this.targetCls);
23359         }
23360         this.initialized = true;
23361     },
23362
23363     // @private Sets the layout owner
23364     setOwner : function(owner) {
23365         this.owner = owner;
23366     },
23367
23368     // @private - Returns empty array
23369     getLayoutItems : function() {
23370         return [];
23371     },
23372
23373     /**
23374      * @private
23375      * Applies itemCls
23376      */
23377     configureItem: function(item) {
23378         var me = this,
23379             el = item.el,
23380             owner = me.owner;
23381             
23382         if (me.itemCls) {
23383             el.addCls(me.itemCls);
23384         }
23385         if (owner.itemCls) {
23386             el.addCls(owner.itemCls);
23387         }
23388     },
23389     
23390     // Placeholder empty functions for subclasses to extend
23391     onLayout : Ext.emptyFn,
23392     afterLayout : Ext.emptyFn,
23393     onRemove : Ext.emptyFn,
23394     onDestroy : Ext.emptyFn,
23395     doOwnerCtLayouts : Ext.emptyFn,
23396
23397     /**
23398      * @private
23399      * Removes itemCls
23400      */
23401     afterRemove : function(item) {
23402         var me = this,
23403             el = item.el,
23404             owner = me.owner;
23405             
23406         if (item.rendered) {
23407             if (me.itemCls) {
23408                 el.removeCls(me.itemCls);
23409             }
23410             if (owner.itemCls) {
23411                 el.removeCls(owner.itemCls);
23412             }
23413         }
23414     },
23415
23416     /*
23417      * Destroys this layout. This is a template method that is empty by default, but should be implemented
23418      * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
23419      * @protected
23420      */
23421     destroy : function() {
23422         if (!Ext.isEmpty(this.targetCls)) {
23423             var target = this.getTarget();
23424             if (target) {
23425                 target.removeCls(this.targetCls);
23426             }
23427         }
23428         this.onDestroy();
23429     }
23430 });
23431 /**
23432  * @class Ext.layout.component.Component
23433  * @extends Ext.layout.Layout
23434  * @private
23435  * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Component#componentLayout layout}</b></tt>
23436  * configuration property.  See <tt><b>{@link Ext.Component#componentLayout}</b></tt> for additional details.</p>
23437  */
23438
23439 Ext.define('Ext.layout.component.Component', {
23440
23441     /* Begin Definitions */
23442
23443     extend: 'Ext.layout.Layout',
23444
23445     /* End Definitions */
23446
23447     type: 'component',
23448
23449     monitorChildren: true,
23450
23451     initLayout : function() {
23452         var me = this,
23453             owner = me.owner,
23454             ownerEl = owner.el;
23455
23456         if (!me.initialized) {
23457             if (owner.frameSize) {
23458                 me.frameSize = owner.frameSize;
23459             }
23460             else {
23461                 owner.frameSize = me.frameSize = {
23462                     top: 0,
23463                     left: 0,
23464                     bottom: 0,
23465                     right: 0
23466                 }; 
23467             }
23468         }
23469         me.callParent(arguments);
23470     },
23471
23472     beforeLayout : function(width, height, isSetSize, layoutOwner) {
23473         this.callParent(arguments);
23474
23475         var me = this,
23476             owner = me.owner,
23477             ownerCt = owner.ownerCt,
23478             layout = owner.layout,
23479             isVisible = owner.isVisible(true),
23480             ownerElChild = owner.el.child,
23481             layoutCollection;
23482
23483         /*
23484          * Do not layout calculatedSized components for fixedLayouts unless the ownerCt == layoutOwner
23485          * fixedLayouts means layouts which are never auto/auto in the sizing that comes from their ownerCt.
23486          * Currently 3 layouts MAY be auto/auto (Auto, Border, and Box)
23487          * The reason for not allowing component layouts is to stop component layouts from things such as Updater and
23488          * form Validation.
23489          */
23490         if (!isSetSize && !(Ext.isNumber(width) && Ext.isNumber(height)) && ownerCt && ownerCt.layout && ownerCt.layout.fixedLayout && ownerCt != layoutOwner) {
23491             me.doContainerLayout();
23492             return false;
23493         }
23494
23495         // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
23496         // If the owner itself is a directly hidden floater, set the needsLayout object on that for when it is shown.
23497         if (!isVisible && (owner.hiddenAncestor || owner.floating)) {
23498             if (owner.hiddenAncestor) {
23499                 layoutCollection = owner.hiddenAncestor.layoutOnShow;
23500                 layoutCollection.remove(owner);
23501                 layoutCollection.add(owner);
23502             }
23503             owner.needsLayout = {
23504                 width: width,
23505                 height: height,
23506                 isSetSize: false
23507             };
23508         }
23509
23510         if (isVisible && this.needsLayout(width, height)) {
23511             me.rawWidth = width;
23512             me.rawHeight = height;
23513             return owner.beforeComponentLayout(width, height, isSetSize, layoutOwner);
23514         }
23515         else {
23516             return false;
23517         }
23518     },
23519
23520     /**
23521     * Check if the new size is different from the current size and only
23522     * trigger a layout if it is necessary.
23523     * @param {Mixed} width The new width to set.
23524     * @param {Mixed} height The new height to set.
23525     */
23526     needsLayout : function(width, height) {
23527         this.lastComponentSize = this.lastComponentSize || {
23528             width: -Infinity,
23529             height: -Infinity
23530         };
23531         return (this.childrenChanged || this.lastComponentSize.width !== width || this.lastComponentSize.height !== height);
23532     },
23533
23534     /**
23535     * Set the size of any element supporting undefined, null, and values.
23536     * @param {Mixed} width The new width to set.
23537     * @param {Mixed} height The new height to set.
23538     */
23539     setElementSize: function(el, width, height) {
23540         if (width !== undefined && height !== undefined) {
23541             el.setSize(width, height);
23542         }
23543         else if (height !== undefined) {
23544             el.setHeight(height);
23545         }
23546         else if (width !== undefined) {
23547             el.setWidth(width);
23548         }
23549     },
23550
23551     /**
23552      * Returns the owner component's resize element.
23553      * @return {Ext.core.Element}
23554      */
23555      getTarget : function() {
23556          return this.owner.el;
23557      },
23558
23559     /**
23560      * <p>Returns the element into which rendering must take place. Defaults to the owner Component's encapsulating element.</p>
23561      * May be overridden in Component layout managers which implement an inner element.
23562      * @return {Ext.core.Element}
23563      */
23564     getRenderTarget : function() {
23565         return this.owner.el;
23566     },
23567
23568     /**
23569     * Set the size of the target element.
23570     * @param {Mixed} width The new width to set.
23571     * @param {Mixed} height The new height to set.
23572     */
23573     setTargetSize : function(width, height) {
23574         var me = this;
23575         me.setElementSize(me.owner.el, width, height);
23576
23577         if (me.owner.frameBody) {
23578             var targetInfo = me.getTargetInfo(),
23579                 padding = targetInfo.padding,
23580                 border = targetInfo.border,
23581                 frameSize = me.frameSize;
23582
23583             me.setElementSize(me.owner.frameBody,
23584                 Ext.isNumber(width) ? (width - frameSize.left - frameSize.right - padding.left - padding.right - border.left - border.right) : width,
23585                 Ext.isNumber(height) ? (height - frameSize.top - frameSize.bottom - padding.top - padding.bottom - border.top - border.bottom) : height
23586             );
23587         }
23588
23589         me.autoSized = {
23590             width: !Ext.isNumber(width),
23591             height: !Ext.isNumber(height)
23592         };
23593
23594         me.lastComponentSize = {
23595             width: width,
23596             height: height
23597         };
23598     },
23599
23600     getTargetInfo : function() {
23601         if (!this.targetInfo) {
23602             var target = this.getTarget(),
23603                 body = this.owner.getTargetEl();
23604
23605             this.targetInfo = {
23606                 padding: {
23607                     top: target.getPadding('t'),
23608                     right: target.getPadding('r'),
23609                     bottom: target.getPadding('b'),
23610                     left: target.getPadding('l')
23611                 },
23612                 border: {
23613                     top: target.getBorderWidth('t'),
23614                     right: target.getBorderWidth('r'),
23615                     bottom: target.getBorderWidth('b'),
23616                     left: target.getBorderWidth('l')
23617                 },
23618                 bodyMargin: {
23619                     top: body.getMargin('t'),
23620                     right: body.getMargin('r'),
23621                     bottom: body.getMargin('b'),
23622                     left: body.getMargin('l')
23623                 } 
23624             };
23625         }
23626         return this.targetInfo;
23627     },
23628
23629     // Start laying out UP the ownerCt's layout when flagged to do so.
23630     doOwnerCtLayouts: function() {
23631         var owner = this.owner,
23632             ownerCt = owner.ownerCt,
23633             ownerCtComponentLayout, ownerCtContainerLayout;
23634
23635         if (!ownerCt) {
23636             return;
23637         }
23638
23639         ownerCtComponentLayout = ownerCt.componentLayout;
23640         ownerCtContainerLayout = ownerCt.layout;
23641
23642         if (!owner.floating && ownerCtComponentLayout && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
23643             if (!ownerCt.suspendLayout && ownerCtContainerLayout && !ownerCtContainerLayout.layoutBusy) {
23644                 // AutoContainer Layout and Dock with auto in some dimension
23645                 if (ownerCtContainerLayout.bindToOwnerCtComponent === true) {
23646                     ownerCt.doComponentLayout();
23647                 }
23648                 // Box Layouts
23649                 else if (ownerCtContainerLayout.bindToOwnerCtContainer === true) {
23650                     ownerCtContainerLayout.layout();
23651                 }
23652             }
23653         }
23654     },
23655
23656     doContainerLayout: function() {
23657         var me = this,
23658             owner = me.owner,
23659             ownerCt = owner.ownerCt,
23660             layout = owner.layout,
23661             ownerCtComponentLayout;
23662
23663         // Run the container layout if it exists (layout for child items)
23664         // **Unless automatic laying out is suspended, or the layout is currently running**
23665         if (!owner.suspendLayout && layout && layout.isLayout && !layout.layoutBusy) {
23666             layout.layout();
23667         }
23668
23669         // Tell the ownerCt that it's child has changed and can be re-layed by ignoring the lastComponentSize cache.
23670         if (ownerCt && ownerCt.componentLayout) {
23671             ownerCtComponentLayout = ownerCt.componentLayout;
23672             if (!owner.floating && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
23673                 ownerCtComponentLayout.childrenChanged = true;
23674             }
23675         }
23676     },
23677
23678     afterLayout : function(width, height, isSetSize, layoutOwner) {
23679         this.doContainerLayout();
23680         this.owner.afterComponentLayout(width, height, isSetSize, layoutOwner);
23681     }
23682 });
23683
23684 /**
23685  * @class Ext.state.Manager
23686  * This is the global state manager. By default all components that are "state aware" check this class
23687  * for state information if you don't pass them a custom state provider. In order for this class
23688  * to be useful, it must be initialized with a provider when your application initializes. Example usage:
23689  <pre><code>
23690 // in your initialization function
23691 init : function(){
23692    Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
23693    var win = new Window(...);
23694    win.restoreState();
23695 }
23696  </code></pre>
23697  * This class passes on calls from components to the underlying {@link Ext.state.Provider} so that
23698  * there is a common interface that can be used without needing to refer to a specific provider instance
23699  * in every component.
23700  * @singleton
23701  * @docauthor Evan Trimboli <evan@sencha.com>
23702  */
23703 Ext.define('Ext.state.Manager', {
23704     singleton: true,
23705     requires: ['Ext.state.Provider'],
23706     constructor: function() {
23707         this.provider = Ext.create('Ext.state.Provider');
23708     },
23709     
23710     
23711     /**
23712      * Configures the default state provider for your application
23713      * @param {Provider} stateProvider The state provider to set
23714      */
23715     setProvider : function(stateProvider){
23716         this.provider = stateProvider;
23717     },
23718
23719     /**
23720      * Returns the current value for a key
23721      * @param {String} name The key name
23722      * @param {Mixed} defaultValue The default value to return if the key lookup does not match
23723      * @return {Mixed} The state data
23724      */
23725     get : function(key, defaultValue){
23726         return this.provider.get(key, defaultValue);
23727     },
23728
23729     /**
23730      * Sets the value for a key
23731      * @param {String} name The key name
23732      * @param {Mixed} value The state data
23733      */
23734      set : function(key, value){
23735         this.provider.set(key, value);
23736     },
23737
23738     /**
23739      * Clears a value from the state
23740      * @param {String} name The key name
23741      */
23742     clear : function(key){
23743         this.provider.clear(key);
23744     },
23745
23746     /**
23747      * Gets the currently configured state provider
23748      * @return {Provider} The state provider
23749      */
23750     getProvider : function(){
23751         return this.provider;
23752     }
23753 });
23754 /**
23755  * @class Ext.state.Stateful
23756  * A mixin for being able to save the state of an object to an underlying 
23757  * {@link Ext.state.Provider}.
23758  */
23759 Ext.define('Ext.state.Stateful', {
23760     
23761     /* Begin Definitions */
23762    
23763    mixins: {
23764         observable: 'Ext.util.Observable'
23765     },
23766     
23767     requires: ['Ext.state.Manager'],
23768     
23769     /* End Definitions */
23770     
23771     /**
23772      * @cfg {Boolean} stateful
23773      * <p>A flag which causes the object to attempt to restore the state of
23774      * internal properties from a saved state on startup. The object must have
23775      * a <code>{@link #stateId}</code> for state to be managed. 
23776      * Auto-generated ids are not guaranteed to be stable across page loads and 
23777      * cannot be relied upon to save and restore the same state for a object.<p>
23778      * <p>For state saving to work, the state manager's provider must have been
23779      * set to an implementation of {@link Ext.state.Provider} which overrides the
23780      * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
23781      * methods to save and recall name/value pairs. A built-in implementation,
23782      * {@link Ext.state.CookieProvider} is available.</p>
23783      * <p>To set the state provider for the current page:</p>
23784      * <pre><code>
23785 Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
23786     expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
23787 }));
23788      * </code></pre>
23789      * <p>A stateful object attempts to save state when one of the events
23790      * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
23791      * <p>To save state, a stateful object first serializes its state by
23792      * calling <b><code>{@link #getState}</code></b>. By default, this function does
23793      * nothing. The developer must provide an implementation which returns an
23794      * object hash which represents the restorable state of the object.</p>
23795      * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
23796      * which uses the configured {@link Ext.state.Provider} to save the object
23797      * keyed by the <code>{@link stateId}</code></p>.
23798      * <p>During construction, a stateful object attempts to <i>restore</i>
23799      * its state by calling {@link Ext.state.Manager#get} passing the
23800      * <code>{@link #stateId}</code></p>
23801      * <p>The resulting object is passed to <b><code>{@link #applyState}</code></b>.
23802      * The default implementation of <code>{@link #applyState}</code> simply copies
23803      * properties into the object, but a developer may override this to support
23804      * more behaviour.</p>
23805      * <p>You can perform extra processing on state save and restore by attaching
23806      * handlers to the {@link #beforestaterestore}, {@link #staterestore},
23807      * {@link #beforestatesave} and {@link #statesave} events.</p>
23808      */
23809     stateful: true,
23810     
23811     /**
23812      * @cfg {String} stateId
23813      * The unique id for this object to use for state management purposes.
23814      * <p>See {@link #stateful} for an explanation of saving and restoring state.</p>
23815      */
23816     
23817     /**
23818      * @cfg {Array} stateEvents
23819      * <p>An array of events that, when fired, should trigger this object to
23820      * save its state (defaults to none). <code>stateEvents</code> may be any type
23821      * of event supported by this object, including browser or custom events
23822      * (e.g., <tt>['click', 'customerchange']</tt>).</p>
23823      * <p>See <code>{@link #stateful}</code> for an explanation of saving and
23824      * restoring object state.</p>
23825      */
23826     
23827     /**
23828      * @cfg {Number} saveBuffer A buffer to be applied if many state events are fired within
23829      * a short period. Defaults to 100.
23830      */
23831     saveDelay: 100,
23832     
23833     autoGenIdRe: /^((\w+-)|(ext-comp-))\d{4,}$/i,
23834     
23835     constructor: function(config) {
23836         var me = this;
23837         
23838         config = config || {};
23839         if (Ext.isDefined(config.stateful)) {
23840             me.stateful = config.stateful;
23841         }
23842         if (Ext.isDefined(config.saveDelay)) {
23843             me.saveDelay = config.saveDelay;
23844         }
23845         me.stateId = config.stateId;
23846         
23847         if (!me.stateEvents) {
23848             me.stateEvents = [];
23849         }
23850         if (config.stateEvents) {
23851             me.stateEvents.concat(config.stateEvents);
23852         }
23853         this.addEvents(
23854             /**
23855              * @event beforestaterestore
23856              * Fires before the state of the object is restored. Return false from an event handler to stop the restore.
23857              * @param {Ext.state.Stateful} this
23858              * @param {Object} state The hash of state values returned from the StateProvider. If this
23859              * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
23860              * that simply copies property values into this object. The method maybe overriden to
23861              * provide custom state restoration.
23862              */
23863             'beforestaterestore',
23864             
23865             /**
23866              * @event staterestore
23867              * Fires after the state of the object is restored.
23868              * @param {Ext.state.Stateful} this
23869              * @param {Object} state The hash of state values returned from the StateProvider. This is passed
23870              * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
23871              * object. The method maybe overriden to provide custom state restoration.
23872              */
23873             'staterestore',
23874             
23875             /**
23876              * @event beforestatesave
23877              * Fires before the state of the object is saved to the configured state provider. Return false to stop the save.
23878              * @param {Ext.state.Stateful} this
23879              * @param {Object} state The hash of state values. This is determined by calling
23880              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
23881              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
23882              * has a null implementation.
23883              */
23884             'beforestatesave',
23885             
23886             /**
23887              * @event statesave
23888              * Fires after the state of the object is saved to the configured state provider.
23889              * @param {Ext.state.Stateful} this
23890              * @param {Object} state The hash of state values. This is determined by calling
23891              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
23892              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
23893              * has a null implementation.
23894              */
23895             'statesave'
23896         );
23897         me.mixins.observable.constructor.call(me);
23898         if (me.stateful !== false) {
23899             me.initStateEvents();
23900             me.initState();
23901         }
23902     },
23903     
23904     /**
23905      * Initializes any state events for this object.
23906      * @private
23907      */
23908     initStateEvents: function() {
23909         this.addStateEvents(this.stateEvents);
23910     },
23911     
23912     /**
23913      * Add events that will trigger the state to be saved.
23914      * @param {String/Array} events The event name or an array of event names.
23915      */
23916     addStateEvents: function(events){
23917         if (!Ext.isArray(events)) {
23918             events = [events];
23919         }
23920         
23921         var me = this,
23922             i = 0,
23923             len = events.length;
23924             
23925         for (; i < len; ++i) {
23926             me.on(events[i], me.onStateChange, me);
23927         }
23928     },
23929     
23930     /**
23931      * This method is called when any of the {@link #stateEvents} are fired.
23932      * @private
23933      */
23934     onStateChange: function(){
23935         var me = this,
23936             delay = me.saveDelay;
23937         
23938         if (delay > 0) {
23939             if (!me.stateTask) {
23940                 me.stateTask = Ext.create('Ext.util.DelayedTask', me.saveState, me);
23941             }
23942             me.stateTask.delay(me.saveDelay);
23943         } else {
23944             me.saveState();
23945         }
23946     },
23947     
23948     /**
23949      * Saves the state of the object to the persistence store.
23950      * @private
23951      */
23952     saveState: function() {
23953         var me = this,
23954             id,
23955             state;
23956         
23957         if (me.stateful !== false) {
23958             id = me.getStateId();
23959             if (id) {
23960                 state = me.getState();
23961                 if (me.fireEvent('beforestatesave', me, state) !== false) {
23962                     Ext.state.Manager.set(id, state);
23963                     me.fireEvent('statesave', me, state);
23964                 }
23965             }
23966         }
23967     },
23968     
23969     /**
23970      * Gets the current state of the object. By default this function returns null,
23971      * it should be overridden in subclasses to implement methods for getting the state.
23972      * @return {Object} The current state
23973      */
23974     getState: function(){
23975         return null;    
23976     },
23977     
23978     /**
23979      * Applies the state to the object. This should be overridden in subclasses to do
23980      * more complex state operations. By default it applies the state properties onto
23981      * the current object.
23982      * @param {Object} state The state
23983      */
23984     applyState: function(state) {
23985         if (state) {
23986             Ext.apply(this, state);
23987         }
23988     },
23989     
23990     /**
23991      * Gets the state id for this object.
23992      * @return {String} The state id, null if not found.
23993      */
23994     getStateId: function() {
23995         var me = this,
23996             id = me.stateId;
23997         
23998         if (!id) {
23999             id = me.autoGenIdRe.test(String(me.id)) ? null : me.id;
24000         }
24001         return id;
24002     },
24003     
24004     /**
24005      * Initializes the state of the object upon construction.
24006      * @private
24007      */
24008     initState: function(){
24009         var me = this,
24010             id = me.getStateId(),
24011             state;
24012             
24013         if (me.stateful !== false) {
24014             if (id) {
24015                 state = Ext.state.Manager.get(id);
24016                 if (state) {
24017                     state = Ext.apply({}, state);
24018                     if (me.fireEvent('beforestaterestore', me, state) !== false) {
24019                         me.applyState(state);
24020                         me.fireEvent('staterestore', me, state);
24021                     }
24022                 }
24023             }
24024         }
24025     },
24026     
24027     /**
24028      * Destroys this stateful object.
24029      */
24030     destroy: function(){
24031         var task = this.stateTask;
24032         if (task) {
24033             task.cancel();
24034         }
24035         this.clearListeners();
24036         
24037     }
24038     
24039 });
24040
24041 /**
24042  * @class Ext.AbstractManager
24043  * @extends Object
24044  * @ignore
24045  * Base Manager class
24046  */
24047
24048 Ext.define('Ext.AbstractManager', {
24049
24050     /* Begin Definitions */
24051
24052     requires: ['Ext.util.HashMap'],
24053
24054     /* End Definitions */
24055
24056     typeName: 'type',
24057
24058     constructor: function(config) {
24059         Ext.apply(this, config || {});
24060
24061         /**
24062          * Contains all of the items currently managed
24063          * @property all
24064          * @type Ext.util.MixedCollection
24065          */
24066         this.all = Ext.create('Ext.util.HashMap');
24067
24068         this.types = {};
24069     },
24070
24071     /**
24072      * Returns an item by id.
24073      * For additional details see {@link Ext.util.HashMap#get}.
24074      * @param {String} id The id of the item
24075      * @return {Mixed} The item, <code>undefined</code> if not found.
24076      */
24077     get : function(id) {
24078         return this.all.get(id);
24079     },
24080
24081     /**
24082      * Registers an item to be managed
24083      * @param {Mixed} item The item to register
24084      */
24085     register: function(item) {
24086         this.all.add(item);
24087     },
24088
24089     /**
24090      * Unregisters an item by removing it from this manager
24091      * @param {Mixed} item The item to unregister
24092      */
24093     unregister: function(item) {
24094         this.all.remove(item);
24095     },
24096
24097     /**
24098      * <p>Registers a new item constructor, keyed by a type key.
24099      * @param {String} type The mnemonic string by which the class may be looked up.
24100      * @param {Constructor} cls The new instance class.
24101      */
24102     registerType : function(type, cls) {
24103         this.types[type] = cls;
24104         cls[this.typeName] = type;
24105     },
24106
24107     /**
24108      * Checks if an item type is registered.
24109      * @param {String} type The mnemonic string by which the class may be looked up
24110      * @return {Boolean} Whether the type is registered.
24111      */
24112     isRegistered : function(type){
24113         return this.types[type] !== undefined;
24114     },
24115
24116     /**
24117      * Creates and returns an instance of whatever this manager manages, based on the supplied type and config object
24118      * @param {Object} config The config object
24119      * @param {String} defaultType If no type is discovered in the config object, we fall back to this type
24120      * @return {Mixed} The instance of whatever this manager is managing
24121      */
24122     create: function(config, defaultType) {
24123         var type        = config[this.typeName] || config.type || defaultType,
24124             Constructor = this.types[type];
24125
24126         if (Constructor == undefined) {
24127             Ext.Error.raise("The '" + type + "' type has not been registered with this manager");
24128         }
24129
24130         return new Constructor(config);
24131     },
24132
24133     /**
24134      * Registers a function that will be called when an item with the specified id is added to the manager. This will happen on instantiation.
24135      * @param {String} id The item id
24136      * @param {Function} fn The callback function. Called with a single parameter, the item.
24137      * @param {Object} scope The scope (<code>this</code> reference) in which the callback is executed. Defaults to the item.
24138      */
24139     onAvailable : function(id, fn, scope){
24140         var all = this.all,
24141             item;
24142         
24143         if (all.containsKey(id)) {
24144             item = all.get(id);
24145             fn.call(scope || item, item);
24146         } else {
24147             all.on('add', function(map, key, item){
24148                 if (key == id) {
24149                     fn.call(scope || item, item);
24150                     all.un('add', fn, scope);
24151                 }
24152             });
24153         }
24154     },
24155     
24156     /**
24157      * Executes the specified function once for each item in the collection.
24158      * Returning false from the function will cease iteration.
24159      * 
24160      * The paramaters passed to the function are:
24161      * <div class="mdetail-params"><ul>
24162      * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
24163      * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
24164      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
24165      * </ul></div>
24166      * @param {Object} fn The function to execute.
24167      * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
24168      */
24169     each: function(fn, scope){
24170         this.all.each(fn, scope || this);    
24171     },
24172     
24173     /**
24174      * Gets the number of items in the collection.
24175      * @return {Number} The number of items in the collection.
24176      */
24177     getCount: function(){
24178         return this.all.getCount();
24179     }
24180 });
24181
24182 /**
24183  * @class Ext.PluginManager
24184  * @extends Ext.AbstractManager
24185  * <p>Provides a registry of available Plugin <i>classes</i> indexed by a mnemonic code known as the Plugin's ptype.
24186  * The <code>{@link Ext.Component#xtype xtype}</code> provides a way to avoid instantiating child Components
24187  * when creating a full, nested config object for a complete Ext page.</p>
24188  * <p>A child Component may be specified simply as a <i>config object</i>
24189  * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
24190  * needs rendering, the correct type can be looked up for lazy instantiation.</p>
24191  * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
24192  * @singleton
24193  */
24194 Ext.define('Ext.PluginManager', {
24195     extend: 'Ext.AbstractManager',
24196     alternateClassName: 'Ext.PluginMgr',
24197     singleton: true,
24198     typeName: 'ptype',
24199
24200     /**
24201      * Creates a new Plugin from the specified config object using the
24202      * config object's ptype to determine the class to instantiate.
24203      * @param {Object} config A configuration object for the Plugin you wish to create.
24204      * @param {Constructor} defaultType The constructor to provide the default Plugin type if
24205      * the config object does not contain a <code>ptype</code>. (Optional if the config contains a <code>ptype</code>).
24206      * @return {Ext.Component} The newly instantiated Plugin.
24207      */
24208     //create: function(plugin, defaultType) {
24209     //    if (plugin instanceof this) {
24210     //        return plugin;
24211     //    } else {
24212     //        var type, config = {};
24213     //
24214     //        if (Ext.isString(plugin)) {
24215     //            type = plugin;
24216     //        }
24217     //        else {
24218     //            type = plugin[this.typeName] || defaultType;
24219     //            config = plugin;
24220     //        }
24221     //
24222     //        return Ext.createByAlias('plugin.' + type, config);
24223     //    }
24224     //},
24225
24226     create : function(config, defaultType){
24227         if (config.init) {
24228             return config;
24229         } else {
24230             return Ext.createByAlias('plugin.' + (config.ptype || defaultType), config);
24231         }
24232         
24233         // Prior system supported Singleton plugins.
24234         //var PluginCls = this.types[config.ptype || defaultType];
24235         //if (PluginCls.init) {
24236         //    return PluginCls;
24237         //} else {
24238         //    return new PluginCls(config);
24239         //}
24240     },
24241
24242     /**
24243      * Returns all plugins registered with the given type. Here, 'type' refers to the type of plugin, not its ptype.
24244      * @param {String} type The type to search for
24245      * @param {Boolean} defaultsOnly True to only return plugins of this type where the plugin's isDefault property is truthy
24246      * @return {Array} All matching plugins
24247      */
24248     findByType: function(type, defaultsOnly) {
24249         var matches = [],
24250             types   = this.types;
24251
24252         for (var name in types) {
24253             if (!types.hasOwnProperty(name)) {
24254                 continue;
24255             }
24256             var item = types[name];
24257
24258             if (item.type == type && (!defaultsOnly || (defaultsOnly === true && item.isDefault))) {
24259                 matches.push(item);
24260             }
24261         }
24262
24263         return matches;
24264     }
24265 }, function() {    
24266     /**
24267      * Shorthand for {@link Ext.PluginManager#registerType}
24268      * @param {String} ptype The ptype mnemonic string by which the Plugin class
24269      * may be looked up.
24270      * @param {Constructor} cls The new Plugin class.
24271      * @member Ext
24272      * @method preg
24273      */
24274     Ext.preg = function() {
24275         return Ext.PluginManager.registerType.apply(Ext.PluginManager, arguments);
24276     };
24277 });
24278
24279 /**
24280  * @class Ext.ComponentManager
24281  * @extends Ext.AbstractManager
24282  * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
24283  * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
24284  * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
24285  * <p>This object also provides a registry of available Component <i>classes</i>
24286  * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
24287  * The <code>xtype</code> provides a way to avoid instantiating child Components
24288  * when creating a full, nested config object for a complete Ext page.</p>
24289  * <p>A child Component may be specified simply as a <i>config object</i>
24290  * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
24291  * needs rendering, the correct type can be looked up for lazy instantiation.</p>
24292  * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
24293  * @singleton
24294  */
24295 Ext.define('Ext.ComponentManager', {
24296     extend: 'Ext.AbstractManager',
24297     alternateClassName: 'Ext.ComponentMgr',
24298     
24299     singleton: true,
24300     
24301     typeName: 'xtype',
24302     
24303     /**
24304      * Creates a new Component from the specified config object using the
24305      * config object's xtype to determine the class to instantiate.
24306      * @param {Object} config A configuration object for the Component you wish to create.
24307      * @param {Constructor} defaultType The constructor to provide the default Component type if
24308      * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
24309      * @return {Ext.Component} The newly instantiated Component.
24310      */
24311     create: function(component, defaultType){
24312         if (component instanceof Ext.AbstractComponent) {
24313             return component;
24314         }
24315         else if (Ext.isString(component)) {
24316             return Ext.createByAlias('widget.' + component);
24317         }
24318         else {
24319             var type = component.xtype || defaultType,
24320                 config = component;
24321             
24322             return Ext.createByAlias('widget.' + type, config);
24323         }
24324     },
24325
24326     registerType: function(type, cls) {
24327         this.types[type] = cls;
24328         cls[this.typeName] = type;
24329         cls.prototype[this.typeName] = type;
24330     }
24331 });
24332 /**
24333  * @class Ext.XTemplate
24334  * @extends Ext.Template
24335  * <p>A template class that supports advanced functionality like:<div class="mdetail-params"><ul>
24336  * <li>Autofilling arrays using templates and sub-templates</li>
24337  * <li>Conditional processing with basic comparison operators</li>
24338  * <li>Basic math function support</li>
24339  * <li>Execute arbitrary inline code with special built-in template variables</li>
24340  * <li>Custom member functions</li>
24341  * <li>Many special tags and built-in operators that aren't defined as part of
24342  * the API, but are supported in the templates that can be created</li>
24343  * </ul></div></p>
24344  * <p>XTemplate provides the templating mechanism built into:<div class="mdetail-params"><ul>
24345  * <li>{@link Ext.view.View}</li>
24346  * </ul></div></p>
24347  *
24348  * The {@link Ext.Template} describes
24349  * the acceptable parameters to pass to the constructor. The following
24350  * examples demonstrate all of the supported features.</p>
24351  *
24352  * <div class="mdetail-params"><ul>
24353  *
24354  * <li><b><u>Sample Data</u></b>
24355  * <div class="sub-desc">
24356  * <p>This is the data object used for reference in each code example:</p>
24357  * <pre><code>
24358 var data = {
24359 name: 'Tommy Maintz',
24360 title: 'Lead Developer',
24361 company: 'Sencha Inc.',
24362 email: 'tommy@sencha.com',
24363 address: '5 Cups Drive',
24364 city: 'Palo Alto',
24365 state: 'CA',
24366 zip: '44102',
24367 drinks: ['Coffee', 'Soda', 'Water'],
24368 kids: [{
24369         name: 'Joshua',
24370         age:3
24371     },{
24372         name: 'Matthew',
24373         age:2
24374     },{
24375         name: 'Solomon',
24376         age:0
24377 }]
24378 };
24379  </code></pre>
24380  * </div>
24381  * </li>
24382  *
24383  *
24384  * <li><b><u>Auto filling of arrays</u></b>
24385  * <div class="sub-desc">
24386  * <p>The <b><tt>tpl</tt></b> tag and the <b><tt>for</tt></b> operator are used
24387  * to process the provided data object:
24388  * <ul>
24389  * <li>If the value specified in <tt>for</tt> is an array, it will auto-fill,
24390  * repeating the template block inside the <tt>tpl</tt> tag for each item in the
24391  * array.</li>
24392  * <li>If <tt>for="."</tt> is specified, the data object provided is examined.</li>
24393  * <li>While processing an array, the special variable <tt>{#}</tt>
24394  * will provide the current array index + 1 (starts at 1, not 0).</li>
24395  * </ul>
24396  * </p>
24397  * <pre><code>
24398 &lt;tpl <b>for</b>=".">...&lt;/tpl>       // loop through array at root node
24399 &lt;tpl <b>for</b>="foo">...&lt;/tpl>     // loop through array at foo node
24400 &lt;tpl <b>for</b>="foo.bar">...&lt;/tpl> // loop through array at foo.bar node
24401  </code></pre>
24402  * Using the sample data above:
24403  * <pre><code>
24404 var tpl = new Ext.XTemplate(
24405     '&lt;p>Kids: ',
24406     '&lt;tpl <b>for</b>=".">',       // process the data.kids node
24407         '&lt;p>{#}. {name}&lt;/p>',  // use current array index to autonumber
24408     '&lt;/tpl>&lt;/p>'
24409 );
24410 tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
24411  </code></pre>
24412  * <p>An example illustrating how the <b><tt>for</tt></b> property can be leveraged
24413  * to access specified members of the provided data object to populate the template:</p>
24414  * <pre><code>
24415 var tpl = new Ext.XTemplate(
24416     '&lt;p>Name: {name}&lt;/p>',
24417     '&lt;p>Title: {title}&lt;/p>',
24418     '&lt;p>Company: {company}&lt;/p>',
24419     '&lt;p>Kids: ',
24420     '&lt;tpl <b>for="kids"</b>>',     // interrogate the kids property within the data
24421         '&lt;p>{name}&lt;/p>',
24422     '&lt;/tpl>&lt;/p>'
24423 );
24424 tpl.overwrite(panel.body, data);  // pass the root node of the data object
24425  </code></pre>
24426  * <p>Flat arrays that contain values (and not objects) can be auto-rendered
24427  * using the special <b><tt>{.}</tt></b> variable inside a loop.  This variable
24428  * will represent the value of the array at the current index:</p>
24429  * <pre><code>
24430 var tpl = new Ext.XTemplate(
24431     '&lt;p>{name}\&#39;s favorite beverages:&lt;/p>',
24432     '&lt;tpl for="drinks">',
24433         '&lt;div> - {.}&lt;/div>',
24434     '&lt;/tpl>'
24435 );
24436 tpl.overwrite(panel.body, data);
24437  </code></pre>
24438  * <p>When processing a sub-template, for example while looping through a child array,
24439  * you can access the parent object's members via the <b><tt>parent</tt></b> object:</p>
24440  * <pre><code>
24441 var tpl = new Ext.XTemplate(
24442     '&lt;p>Name: {name}&lt;/p>',
24443     '&lt;p>Kids: ',
24444     '&lt;tpl for="kids">',
24445         '&lt;tpl if="age &amp;gt; 1">',
24446             '&lt;p>{name}&lt;/p>',
24447             '&lt;p>Dad: {<b>parent</b>.name}&lt;/p>',
24448         '&lt;/tpl>',
24449     '&lt;/tpl>&lt;/p>'
24450 );
24451 tpl.overwrite(panel.body, data);
24452  </code></pre>
24453  * </div>
24454  * </li>
24455  *
24456  *
24457  * <li><b><u>Conditional processing with basic comparison operators</u></b>
24458  * <div class="sub-desc">
24459  * <p>The <b><tt>tpl</tt></b> tag and the <b><tt>if</tt></b> operator are used
24460  * to provide conditional checks for deciding whether or not to render specific
24461  * parts of the template. Notes:<div class="sub-desc"><ul>
24462  * <li>Double quotes must be encoded if used within the conditional</li>
24463  * <li>There is no <tt>else</tt> operator &mdash; if needed, two opposite
24464  * <tt>if</tt> statements should be used.</li>
24465  * </ul></div>
24466  * <pre><code>
24467 &lt;tpl if="age &gt; 1 &amp;&amp; age &lt; 10">Child&lt;/tpl>
24468 &lt;tpl if="age >= 10 && age < 18">Teenager&lt;/tpl>
24469 &lt;tpl <b>if</b>="this.isGirl(name)">...&lt;/tpl>
24470 &lt;tpl <b>if</b>="id==\'download\'">...&lt;/tpl>
24471 &lt;tpl <b>if</b>="needsIcon">&lt;img src="{icon}" class="{iconCls}"/>&lt;/tpl>
24472 // no good:
24473 &lt;tpl if="name == "Tommy"">Hello&lt;/tpl>
24474 // encode &#34; if it is part of the condition, e.g.
24475 &lt;tpl if="name == &#38;quot;Tommy&#38;quot;">Hello&lt;/tpl>
24476  * </code></pre>
24477  * Using the sample data above:
24478  * <pre><code>
24479 var tpl = new Ext.XTemplate(
24480     '&lt;p>Name: {name}&lt;/p>',
24481     '&lt;p>Kids: ',
24482     '&lt;tpl for="kids">',
24483         '&lt;tpl if="age &amp;gt; 1">',
24484             '&lt;p>{name}&lt;/p>',
24485         '&lt;/tpl>',
24486     '&lt;/tpl>&lt;/p>'
24487 );
24488 tpl.overwrite(panel.body, data);
24489  </code></pre>
24490  * </div>
24491  * </li>
24492  *
24493  *
24494  * <li><b><u>Basic math support</u></b>
24495  * <div class="sub-desc">
24496  * <p>The following basic math operators may be applied directly on numeric
24497  * data values:</p><pre>
24498  * + - * /
24499  * </pre>
24500  * For example:
24501  * <pre><code>
24502 var tpl = new Ext.XTemplate(
24503     '&lt;p>Name: {name}&lt;/p>',
24504     '&lt;p>Kids: ',
24505     '&lt;tpl for="kids">',
24506         '&lt;tpl if="age &amp;gt; 1">',  // <-- Note that the &gt; is encoded
24507             '&lt;p>{#}: {name}&lt;/p>',  // <-- Auto-number each item
24508             '&lt;p>In 5 Years: {age+5}&lt;/p>',  // <-- Basic math
24509             '&lt;p>Dad: {parent.name}&lt;/p>',
24510         '&lt;/tpl>',
24511     '&lt;/tpl>&lt;/p>'
24512 );
24513 tpl.overwrite(panel.body, data);
24514  </code></pre>
24515  * </div>
24516  * </li>
24517  *
24518  *
24519  * <li><b><u>Execute arbitrary inline code with special built-in template variables</u></b>
24520  * <div class="sub-desc">
24521  * <p>Anything between <code>{[ ... ]}</code> is considered code to be executed
24522  * in the scope of the template. There are some special variables available in that code:
24523  * <ul>
24524  * <li><b><tt>values</tt></b>: The values in the current scope. If you are using
24525  * scope changing sub-templates, you can change what <tt>values</tt> is.</li>
24526  * <li><b><tt>parent</tt></b>: The scope (values) of the ancestor template.</li>
24527  * <li><b><tt>xindex</tt></b>: If you are in a looping template, the index of the
24528  * loop you are in (1-based).</li>
24529  * <li><b><tt>xcount</tt></b>: If you are in a looping template, the total length
24530  * of the array you are looping.</li>
24531  * </ul>
24532  * This example demonstrates basic row striping using an inline code block and the
24533  * <tt>xindex</tt> variable:</p>
24534  * <pre><code>
24535 var tpl = new Ext.XTemplate(
24536     '&lt;p>Name: {name}&lt;/p>',
24537     '&lt;p>Company: {[values.company.toUpperCase() + ", " + values.title]}&lt;/p>',
24538     '&lt;p>Kids: ',
24539     '&lt;tpl for="kids">',
24540         '&lt;div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
24541         '{name}',
24542         '&lt;/div>',
24543     '&lt;/tpl>&lt;/p>'
24544  );
24545 tpl.overwrite(panel.body, data);
24546  </code></pre>
24547  * </div>
24548  * </li>
24549  *
24550  * <li><b><u>Template member functions</u></b>
24551  * <div class="sub-desc">
24552  * <p>One or more member functions can be specified in a configuration
24553  * object passed into the XTemplate constructor for more complex processing:</p>
24554  * <pre><code>
24555 var tpl = new Ext.XTemplate(
24556     '&lt;p>Name: {name}&lt;/p>',
24557     '&lt;p>Kids: ',
24558     '&lt;tpl for="kids">',
24559         '&lt;tpl if="this.isGirl(name)">',
24560             '&lt;p>Girl: {name} - {age}&lt;/p>',
24561         '&lt;/tpl>',
24562          // use opposite if statement to simulate 'else' processing:
24563         '&lt;tpl if="this.isGirl(name) == false">',
24564             '&lt;p>Boy: {name} - {age}&lt;/p>',
24565         '&lt;/tpl>',
24566         '&lt;tpl if="this.isBaby(age)">',
24567             '&lt;p>{name} is a baby!&lt;/p>',
24568         '&lt;/tpl>',
24569     '&lt;/tpl>&lt;/p>',
24570     {
24571         // XTemplate configuration:
24572         compiled: true,
24573         // member functions:
24574         isGirl: function(name){
24575            return name == 'Sara Grace';
24576         },
24577         isBaby: function(age){
24578            return age < 1;
24579         }
24580     }
24581 );
24582 tpl.overwrite(panel.body, data);
24583  </code></pre>
24584  * </div>
24585  * </li>
24586  *
24587  * </ul></div>
24588  *
24589  * @param {Mixed} config
24590  */
24591
24592 Ext.define('Ext.XTemplate', {
24593
24594     /* Begin Definitions */
24595
24596     extend: 'Ext.Template',
24597
24598     statics: {
24599         /**
24600          * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
24601          * @param {String/HTMLElement} el A DOM element or its id
24602          * @return {Ext.Template} The created template
24603          * @static
24604          */
24605         from: function(el, config) {
24606             el = Ext.getDom(el);
24607             return new this(el.value || el.innerHTML, config || {});
24608         }
24609     },
24610
24611     /* End Definitions */
24612
24613     argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
24614     nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
24615     ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
24616     execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
24617     constructor: function() {
24618         this.callParent(arguments);
24619
24620         var me = this,
24621             html = me.html,
24622             argsRe = me.argsRe,
24623             nameRe = me.nameRe,
24624             ifRe = me.ifRe,
24625             execRe = me.execRe,
24626             id = 0,
24627             tpls = [],
24628             VALUES = 'values',
24629             PARENT = 'parent',
24630             XINDEX = 'xindex',
24631             XCOUNT = 'xcount',
24632             RETURN = 'return ',
24633             WITHVALUES = 'with(values){ ',
24634             m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
24635
24636         html = ['<tpl>', html, '</tpl>'].join('');
24637
24638         while ((m = html.match(argsRe))) {
24639             exp = null;
24640             fn = null;
24641             exec = null;
24642             matchName = m[0].match(nameRe);
24643             matchIf = m[0].match(ifRe);
24644             matchExec = m[0].match(execRe);
24645
24646             exp = matchIf ? matchIf[1] : null;
24647             if (exp) {
24648                 fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
24649             }
24650
24651             exp = matchExec ? matchExec[1] : null;
24652             if (exp) {
24653                 exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
24654             }
24655
24656             name = matchName ? matchName[1] : null;
24657             if (name) {
24658                 if (name === '.') {
24659                     name = VALUES;
24660                 } else if (name === '..') {
24661                     name = PARENT;
24662                 }
24663                 name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
24664             }
24665
24666             tpls.push({
24667                 id: id,
24668                 target: name,
24669                 exec: exec,
24670                 test: fn,
24671                 body: m[1] || ''
24672             });
24673
24674             html = html.replace(m[0], '{xtpl' + id + '}');
24675             id = id + 1;
24676         }
24677
24678         for (i = tpls.length - 1; i >= 0; --i) {
24679             me.compileTpl(tpls[i]);
24680         }
24681         me.master = tpls[tpls.length - 1];
24682         me.tpls = tpls;
24683     },
24684
24685     // @private
24686     applySubTemplate: function(id, values, parent, xindex, xcount) {
24687         var me = this, t = me.tpls[id];
24688         return t.compiled.call(me, values, parent, xindex, xcount);
24689     },
24690     /**
24691      * @cfg {RegExp} codeRe The regular expression used to match code variables (default: matches <tt>{[expression]}</tt>).
24692      */
24693     codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
24694
24695     re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
24696
24697     // @private
24698     compileTpl: function(tpl) {
24699         var fm = Ext.util.Format,
24700             me = this,
24701             useFormat = me.disableFormats !== true,
24702             body, bodyReturn, evaluatedFn;
24703
24704         function fn(m, name, format, args, math) {
24705             var v;
24706             // name is what is inside the {}
24707             // Name begins with xtpl, use a Sub Template
24708             if (name.substr(0, 4) == 'xtpl') {
24709                 return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
24710             }
24711             // name = "." - Just use the values object.
24712             if (name == '.') {
24713                 // filter to not include arrays/objects/nulls
24714                 v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
24715             }
24716
24717             // name = "#" - Use the xindex
24718             else if (name == '#') {
24719                 v = 'xindex';
24720             }
24721             else if (name.substr(0, 7) == "parent.") {
24722                 v = name;
24723             }
24724             // name has a . in it - Use object literal notation, starting from values
24725             else if (name.indexOf('.') != -1) {
24726                 v = "values." + name;
24727             }
24728
24729             // name is a property of values
24730             else {
24731                 v = "values['" + name + "']";
24732             }
24733             if (math) {
24734                 v = '(' + v + math + ')';
24735             }
24736             if (format && useFormat) {
24737                 args = args ? ',' + args : "";
24738                 if (format.substr(0, 5) != "this.") {
24739                     format = "fm." + format + '(';
24740                 }
24741                 else {
24742                     format = 'this.' + format.substr(5) + '(';
24743                 }
24744             }
24745             else {
24746                 args = '';
24747                 format = "(" + v + " === undefined ? '' : ";
24748             }
24749             return "'," + format + v + args + "),'";
24750         }
24751
24752         function codeFn(m, code) {
24753             // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
24754             return "',(" + code.replace(me.compileARe, "'") + "),'";
24755         }
24756
24757         bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
24758         body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
24759         eval(body);
24760
24761         tpl.compiled = function(values, parent, xindex, xcount) {
24762             var vs,
24763                 length,
24764                 buffer,
24765                 i;
24766
24767             if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
24768                 return '';
24769             }
24770
24771             vs = tpl.target ? tpl.target.call(me, values, parent) : values;
24772             if (!vs) {
24773                return '';
24774             }
24775
24776             parent = tpl.target ? values : parent;
24777             if (tpl.target && Ext.isArray(vs)) {
24778                 buffer = [];
24779                 length = vs.length;
24780                 if (tpl.exec) {
24781                     for (i = 0; i < length; i++) {
24782                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
24783                         tpl.exec.call(me, vs[i], parent, i + 1, length);
24784                     }
24785                 } else {
24786                     for (i = 0; i < length; i++) {
24787                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
24788                     }
24789                 }
24790                 return buffer.join('');
24791             }
24792
24793             if (tpl.exec) {
24794                 tpl.exec.call(me, vs, parent, xindex, xcount);
24795             }
24796             return evaluatedFn.call(me, vs, parent, xindex, xcount);
24797         };
24798
24799         return this;
24800     },
24801
24802     /**
24803      * Returns an HTML fragment of this template with the specified values applied.
24804      * @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
24805      * @return {String} The HTML fragment
24806      */
24807     applyTemplate: function(values) {
24808         return this.master.compiled.call(this, values, {}, 1, 1);
24809     },
24810
24811     /**
24812      * Compile the template to a function for optimized performance.  Recommended if the template will be used frequently.
24813      * @return {Function} The compiled function
24814      */
24815     compile: function() {
24816         return this;
24817     }
24818 }, function() {
24819     /**
24820      * Alias for {@link #applyTemplate}
24821      * Returns an HTML fragment of this template with the specified values applied.
24822      * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
24823      * @return {String} The HTML fragment
24824      * @member Ext.XTemplate
24825      * @method apply
24826      */
24827     this.createAlias('apply', 'applyTemplate');
24828 });
24829
24830 /**
24831  * @class Ext.util.AbstractMixedCollection
24832  */
24833 Ext.define('Ext.util.AbstractMixedCollection', {
24834     requires: ['Ext.util.Filter'],
24835     
24836     mixins: {
24837         observable: 'Ext.util.Observable'
24838     },
24839
24840     constructor: function(allowFunctions, keyFn) {
24841         var me = this;
24842
24843         me.items = [];
24844         me.map = {};
24845         me.keys = [];
24846         me.length = 0;
24847
24848         me.addEvents(
24849             /**
24850              * @event clear
24851              * Fires when the collection is cleared.
24852              */
24853             'clear',
24854
24855             /**
24856              * @event add
24857              * Fires when an item is added to the collection.
24858              * @param {Number} index The index at which the item was added.
24859              * @param {Object} o The item added.
24860              * @param {String} key The key associated with the added item.
24861              */
24862             'add',
24863
24864             /**
24865              * @event replace
24866              * Fires when an item is replaced in the collection.
24867              * @param {String} key he key associated with the new added.
24868              * @param {Object} old The item being replaced.
24869              * @param {Object} new The new item.
24870              */
24871             'replace',
24872
24873             /**
24874              * @event remove
24875              * Fires when an item is removed from the collection.
24876              * @param {Object} o The item being removed.
24877              * @param {String} key (optional) The key associated with the removed item.
24878              */
24879             'remove'
24880         );
24881
24882         me.allowFunctions = allowFunctions === true;
24883
24884         if (keyFn) {
24885             me.getKey = keyFn;
24886         }
24887
24888         me.mixins.observable.constructor.call(me);
24889     },
24890     
24891     /**
24892      * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
24893      * function should add function references to the collection. Defaults to
24894      * <tt>false</tt>.
24895      */
24896     allowFunctions : false,
24897
24898     /**
24899      * Adds an item to the collection. Fires the {@link #add} event when complete.
24900      * @param {String} key <p>The key to associate with the item, or the new item.</p>
24901      * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
24902      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
24903      * the MixedCollection will be able to <i>derive</i> the key for the new item.
24904      * In this case just pass the new item in this parameter.</p>
24905      * @param {Object} o The item to add.
24906      * @return {Object} The item added.
24907      */
24908     add : function(key, obj){
24909         var me = this,
24910             myObj = obj,
24911             myKey = key,
24912             old;
24913
24914         if (arguments.length == 1) {
24915             myObj = myKey;
24916             myKey = me.getKey(myObj);
24917         }
24918         if (typeof myKey != 'undefined' && myKey !== null) {
24919             old = me.map[myKey];
24920             if (typeof old != 'undefined') {
24921                 return me.replace(myKey, myObj);
24922             }
24923             me.map[myKey] = myObj;
24924         }
24925         me.length++;
24926         me.items.push(myObj);
24927         me.keys.push(myKey);
24928         me.fireEvent('add', me.length - 1, myObj, myKey);
24929         return myObj;
24930     },
24931
24932     /**
24933       * MixedCollection has a generic way to fetch keys if you implement getKey.  The default implementation
24934       * simply returns <b><code>item.id</code></b> but you can provide your own implementation
24935       * to return a different value as in the following examples:<pre><code>
24936 // normal way
24937 var mc = new Ext.util.MixedCollection();
24938 mc.add(someEl.dom.id, someEl);
24939 mc.add(otherEl.dom.id, otherEl);
24940 //and so on
24941
24942 // using getKey
24943 var mc = new Ext.util.MixedCollection();
24944 mc.getKey = function(el){
24945    return el.dom.id;
24946 };
24947 mc.add(someEl);
24948 mc.add(otherEl);
24949
24950 // or via the constructor
24951 var mc = new Ext.util.MixedCollection(false, function(el){
24952    return el.dom.id;
24953 });
24954 mc.add(someEl);
24955 mc.add(otherEl);
24956      * </code></pre>
24957      * @param {Object} item The item for which to find the key.
24958      * @return {Object} The key for the passed item.
24959      */
24960     getKey : function(o){
24961          return o.id;
24962     },
24963
24964     /**
24965      * Replaces an item in the collection. Fires the {@link #replace} event when complete.
24966      * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
24967      * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
24968      * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
24969      * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
24970      * with one having the same key value, then just pass the replacement item in this parameter.</p>
24971      * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
24972      * with that key.
24973      * @return {Object}  The new item.
24974      */
24975     replace : function(key, o){
24976         var me = this,
24977             old,
24978             index;
24979
24980         if (arguments.length == 1) {
24981             o = arguments[0];
24982             key = me.getKey(o);
24983         }
24984         old = me.map[key];
24985         if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
24986              return me.add(key, o);
24987         }
24988         index = me.indexOfKey(key);
24989         me.items[index] = o;
24990         me.map[key] = o;
24991         me.fireEvent('replace', key, old, o);
24992         return o;
24993     },
24994
24995     /**
24996      * Adds all elements of an Array or an Object to the collection.
24997      * @param {Object/Array} objs An Object containing properties which will be added
24998      * to the collection, or an Array of values, each of which are added to the collection.
24999      * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
25000      * has been set to <tt>true</tt>.
25001      */
25002     addAll : function(objs){
25003         var me = this,
25004             i = 0,
25005             args,
25006             len,
25007             key;
25008
25009         if (arguments.length > 1 || Ext.isArray(objs)) {
25010             args = arguments.length > 1 ? arguments : objs;
25011             for (len = args.length; i < len; i++) {
25012                 me.add(args[i]);
25013             }
25014         } else {
25015             for (key in objs) {
25016                 if (objs.hasOwnProperty(key)) {
25017                     if (me.allowFunctions || typeof objs[key] != 'function') {
25018                         me.add(key, objs[key]);
25019                     }
25020                 }
25021             }
25022         }
25023     },
25024
25025     /**
25026      * Executes the specified function once for every item in the collection, passing the following arguments:
25027      * <div class="mdetail-params"><ul>
25028      * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
25029      * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
25030      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
25031      * </ul></div>
25032      * The function should return a boolean value. Returning false from the function will stop the iteration.
25033      * @param {Function} fn The function to execute for each item.
25034      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
25035      */
25036     each : function(fn, scope){
25037         var items = [].concat(this.items), // each safe for removal
25038             i = 0,
25039             len = items.length,
25040             item;
25041
25042         for (; i < len; i++) {
25043             item = items[i];
25044             if (fn.call(scope || item, item, i, len) === false) {
25045                 break;
25046             }
25047         }
25048     },
25049
25050     /**
25051      * Executes the specified function once for every key in the collection, passing each
25052      * key, and its associated item as the first two parameters.
25053      * @param {Function} fn The function to execute for each item.
25054      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
25055      */
25056     eachKey : function(fn, scope){
25057         var keys = this.keys,
25058             items = this.items,
25059             i = 0,
25060             len = keys.length;
25061
25062         for (; i < len; i++) {
25063             fn.call(scope || window, keys[i], items[i], i, len);
25064         }
25065     },
25066
25067     /**
25068      * Returns the first item in the collection which elicits a true return value from the
25069      * passed selection function.
25070      * @param {Function} fn The selection function to execute for each item.
25071      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
25072      * @return {Object} The first item in the collection which returned true from the selection function.
25073      */
25074     findBy : function(fn, scope) {
25075         var keys = this.keys,
25076             items = this.items,
25077             i = 0,
25078             len = items.length;
25079
25080         for (; i < len; i++) {
25081             if (fn.call(scope || window, items[i], keys[i])) {
25082                 return items[i];
25083             }
25084         }
25085         return null;
25086     },
25087
25088     find : function() {
25089         if (Ext.isDefined(Ext.global.console)) {
25090             Ext.global.console.warn('Ext.util.MixedCollection: find has been deprecated. Use findBy instead.');
25091         }
25092         return this.findBy.apply(this, arguments);
25093     },
25094
25095     /**
25096      * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
25097      * @param {Number} index The index to insert the item at.
25098      * @param {String} key The key to associate with the new item, or the item itself.
25099      * @param {Object} o (optional) If the second parameter was a key, the new item.
25100      * @return {Object} The item inserted.
25101      */
25102     insert : function(index, key, obj){
25103         var me = this,
25104             myKey = key,
25105             myObj = obj;
25106
25107         if (arguments.length == 2) {
25108             myObj = myKey;
25109             myKey = me.getKey(myObj);
25110         }
25111         if (me.containsKey(myKey)) {
25112             me.suspendEvents();
25113             me.removeAtKey(myKey);
25114             me.resumeEvents();
25115         }
25116         if (index >= me.length) {
25117             return me.add(myKey, myObj);
25118         }
25119         me.length++;
25120         me.items.splice(index, 0, myObj);
25121         if (typeof myKey != 'undefined' && myKey !== null) {
25122             me.map[myKey] = myObj;
25123         }
25124         me.keys.splice(index, 0, myKey);
25125         me.fireEvent('add', index, myObj, myKey);
25126         return myObj;
25127     },
25128
25129     /**
25130      * Remove an item from the collection.
25131      * @param {Object} o The item to remove.
25132      * @return {Object} The item removed or false if no item was removed.
25133      */
25134     remove : function(o){
25135         return this.removeAt(this.indexOf(o));
25136     },
25137
25138     /**
25139      * Remove all items in the passed array from the collection.
25140      * @param {Array} items An array of items to be removed.
25141      * @return {Ext.util.MixedCollection} this object
25142      */
25143     removeAll : function(items){
25144         Ext.each(items || [], function(item) {
25145             this.remove(item);
25146         }, this);
25147
25148         return this;
25149     },
25150
25151     /**
25152      * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
25153      * @param {Number} index The index within the collection of the item to remove.
25154      * @return {Object} The item removed or false if no item was removed.
25155      */
25156     removeAt : function(index){
25157         var me = this,
25158             o,
25159             key;
25160
25161         if (index < me.length && index >= 0) {
25162             me.length--;
25163             o = me.items[index];
25164             me.items.splice(index, 1);
25165             key = me.keys[index];
25166             if (typeof key != 'undefined') {
25167                 delete me.map[key];
25168             }
25169             me.keys.splice(index, 1);
25170             me.fireEvent('remove', o, key);
25171             return o;
25172         }
25173         return false;
25174     },
25175
25176     /**
25177      * Removed an item associated with the passed key fom the collection.
25178      * @param {String} key The key of the item to remove.
25179      * @return {Object} The item removed or false if no item was removed.
25180      */
25181     removeAtKey : function(key){
25182         return this.removeAt(this.indexOfKey(key));
25183     },
25184
25185     /**
25186      * Returns the number of items in the collection.
25187      * @return {Number} the number of items in the collection.
25188      */
25189     getCount : function(){
25190         return this.length;
25191     },
25192
25193     /**
25194      * Returns index within the collection of the passed Object.
25195      * @param {Object} o The item to find the index of.
25196      * @return {Number} index of the item. Returns -1 if not found.
25197      */
25198     indexOf : function(o){
25199         return Ext.Array.indexOf(this.items, o);
25200     },
25201
25202     /**
25203      * Returns index within the collection of the passed key.
25204      * @param {String} key The key to find the index of.
25205      * @return {Number} index of the key.
25206      */
25207     indexOfKey : function(key){
25208         return Ext.Array.indexOf(this.keys, key);
25209     },
25210
25211     /**
25212      * Returns the item associated with the passed key OR index.
25213      * Key has priority over index.  This is the equivalent
25214      * of calling {@link #key} first, then if nothing matched calling {@link #getAt}.
25215      * @param {String/Number} key The key or index of the item.
25216      * @return {Object} If the item is found, returns the item.  If the item was not found, returns <tt>undefined</tt>.
25217      * If an item was found, but is a Class, returns <tt>null</tt>.
25218      */
25219     get : function(key) {
25220         var me = this,
25221             mk = me.map[key],
25222             item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
25223         return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
25224     },
25225
25226     /**
25227      * Returns the item at the specified index.
25228      * @param {Number} index The index of the item.
25229      * @return {Object} The item at the specified index.
25230      */
25231     getAt : function(index) {
25232         return this.items[index];
25233     },
25234
25235     /**
25236      * Returns the item associated with the passed key.
25237      * @param {String/Number} key The key of the item.
25238      * @return {Object} The item associated with the passed key.
25239      */
25240     getByKey : function(key) {
25241         return this.map[key];
25242     },
25243
25244     /**
25245      * Returns true if the collection contains the passed Object as an item.
25246      * @param {Object} o  The Object to look for in the collection.
25247      * @return {Boolean} True if the collection contains the Object as an item.
25248      */
25249     contains : function(o){
25250         return Ext.Array.contains(this.items, o);
25251     },
25252
25253     /**
25254      * Returns true if the collection contains the passed Object as a key.
25255      * @param {String} key The key to look for in the collection.
25256      * @return {Boolean} True if the collection contains the Object as a key.
25257      */
25258     containsKey : function(key){
25259         return typeof this.map[key] != 'undefined';
25260     },
25261
25262     /**
25263      * Removes all items from the collection.  Fires the {@link #clear} event when complete.
25264      */
25265     clear : function(){
25266         var me = this;
25267
25268         me.length = 0;
25269         me.items = [];
25270         me.keys = [];
25271         me.map = {};
25272         me.fireEvent('clear');
25273     },
25274
25275     /**
25276      * Returns the first item in the collection.
25277      * @return {Object} the first item in the collection..
25278      */
25279     first : function() {
25280         return this.items[0];
25281     },
25282
25283     /**
25284      * Returns the last item in the collection.
25285      * @return {Object} the last item in the collection..
25286      */
25287     last : function() {
25288         return this.items[this.length - 1];
25289     },
25290
25291     /**
25292      * Collects all of the values of the given property and returns their sum
25293      * @param {String} property The property to sum by
25294      * @param {String} root Optional 'root' property to extract the first argument from. This is used mainly when
25295      * summing fields in records, where the fields are all stored inside the 'data' object
25296      * @param {Number} start (optional) The record index to start at (defaults to <tt>0</tt>)
25297      * @param {Number} end (optional) The record index to end at (defaults to <tt>-1</tt>)
25298      * @return {Number} The total
25299      */
25300     sum: function(property, root, start, end) {
25301         var values = this.extractValues(property, root),
25302             length = values.length,
25303             sum    = 0,
25304             i;
25305
25306         start = start || 0;
25307         end   = (end || end === 0) ? end : length - 1;
25308
25309         for (i = start; i <= end; i++) {
25310             sum += values[i];
25311         }
25312
25313         return sum;
25314     },
25315
25316     /**
25317      * Collects unique values of a particular property in this MixedCollection
25318      * @param {String} property The property to collect on
25319      * @param {String} root Optional 'root' property to extract the first argument from. This is used mainly when
25320      * summing fields in records, where the fields are all stored inside the 'data' object
25321      * @param {Boolean} allowBlank (optional) Pass true to allow null, undefined or empty string values
25322      * @return {Array} The unique values
25323      */
25324     collect: function(property, root, allowNull) {
25325         var values = this.extractValues(property, root),
25326             length = values.length,
25327             hits   = {},
25328             unique = [],
25329             value, strValue, i;
25330
25331         for (i = 0; i < length; i++) {
25332             value = values[i];
25333             strValue = String(value);
25334
25335             if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
25336                 hits[strValue] = true;
25337                 unique.push(value);
25338             }
25339         }
25340
25341         return unique;
25342     },
25343
25344     /**
25345      * @private
25346      * Extracts all of the given property values from the items in the MC. Mainly used as a supporting method for
25347      * functions like sum and collect.
25348      * @param {String} property The property to extract
25349      * @param {String} root Optional 'root' property to extract the first argument from. This is used mainly when
25350      * extracting field data from Model instances, where the fields are stored inside the 'data' object
25351      * @return {Array} The extracted values
25352      */
25353     extractValues: function(property, root) {
25354         var values = this.items;
25355
25356         if (root) {
25357             values = Ext.Array.pluck(values, root);
25358         }
25359
25360         return Ext.Array.pluck(values, property);
25361     },
25362
25363     /**
25364      * Returns a range of items in this collection
25365      * @param {Number} startIndex (optional) The starting index. Defaults to 0.
25366      * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
25367      * @return {Array} An array of items
25368      */
25369     getRange : function(start, end){
25370         var me = this,
25371             items = me.items,
25372             range = [],
25373             i;
25374
25375         if (items.length < 1) {
25376             return range;
25377         }
25378
25379         start = start || 0;
25380         end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
25381         if (start <= end) {
25382             for (i = start; i <= end; i++) {
25383                 range[range.length] = items[i];
25384             }
25385         } else {
25386             for (i = start; i >= end; i--) {
25387                 range[range.length] = items[i];
25388             }
25389         }
25390         return range;
25391     },
25392
25393     /**
25394      * <p>Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
25395      * property/value pair with optional parameters for substring matching and case sensitivity. See
25396      * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
25397      * MixedCollection can be easily filtered by property like this:</p>
25398 <pre><code>
25399 //create a simple store with a few people defined
25400 var people = new Ext.util.MixedCollection();
25401 people.addAll([
25402     {id: 1, age: 25, name: 'Ed'},
25403     {id: 2, age: 24, name: 'Tommy'},
25404     {id: 3, age: 24, name: 'Arne'},
25405     {id: 4, age: 26, name: 'Aaron'}
25406 ]);
25407
25408 //a new MixedCollection containing only the items where age == 24
25409 var middleAged = people.filter('age', 24);
25410 </code></pre>
25411      *
25412      *
25413      * @param {Array/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
25414      * @param {String/RegExp} value Either string that the property values
25415      * should start with or a RegExp to test against the property
25416      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
25417      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison (defaults to False).
25418      * @return {MixedCollection} The new filtered collection
25419      */
25420     filter : function(property, value, anyMatch, caseSensitive) {
25421         var filters = [],
25422             filterFn;
25423
25424         //support for the simple case of filtering by property/value
25425         if (Ext.isString(property)) {
25426             filters.push(Ext.create('Ext.util.Filter', {
25427                 property     : property,
25428                 value        : value,
25429                 anyMatch     : anyMatch,
25430                 caseSensitive: caseSensitive
25431             }));
25432         } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
25433             filters = filters.concat(property);
25434         }
25435
25436         //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
25437         //so here we construct a function that combines these filters by ANDing them together
25438         filterFn = function(record) {
25439             var isMatch = true,
25440                 length = filters.length,
25441                 i;
25442
25443             for (i = 0; i < length; i++) {
25444                 var filter = filters[i],
25445                     fn     = filter.filterFn,
25446                     scope  = filter.scope;
25447
25448                 isMatch = isMatch && fn.call(scope, record);
25449             }
25450
25451             return isMatch;
25452         };
25453
25454         return this.filterBy(filterFn);
25455     },
25456
25457     /**
25458      * Filter by a function. Returns a <i>new</i> collection that has been filtered.
25459      * The passed function will be called with each object in the collection.
25460      * If the function returns true, the value is included otherwise it is filtered.
25461      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
25462      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
25463      * @return {MixedCollection} The new filtered collection
25464      */
25465     filterBy : function(fn, scope) {
25466         var me = this,
25467             newMC  = new this.self(),
25468             keys   = me.keys,
25469             items  = me.items,
25470             length = items.length,
25471             i;
25472
25473         newMC.getKey = me.getKey;
25474
25475         for (i = 0; i < length; i++) {
25476             if (fn.call(scope || me, items[i], keys[i])) {
25477                 newMC.add(keys[i], items[i]);
25478             }
25479         }
25480
25481         return newMC;
25482     },
25483
25484     /**
25485      * Finds the index of the first matching object in this collection by a specific property/value.
25486      * @param {String} property The name of a property on your objects.
25487      * @param {String/RegExp} value A string that the property values
25488      * should start with or a RegExp to test against the property.
25489      * @param {Number} start (optional) The index to start searching at (defaults to 0).
25490      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning.
25491      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison.
25492      * @return {Number} The matched index or -1
25493      */
25494     findIndex : function(property, value, start, anyMatch, caseSensitive){
25495         if(Ext.isEmpty(value, false)){
25496             return -1;
25497         }
25498         value = this.createValueMatcher(value, anyMatch, caseSensitive);
25499         return this.findIndexBy(function(o){
25500             return o && value.test(o[property]);
25501         }, null, start);
25502     },
25503
25504     /**
25505      * Find the index of the first matching object in this collection by a function.
25506      * If the function returns <i>true</i> it is considered a match.
25507      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
25508      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
25509      * @param {Number} start (optional) The index to start searching at (defaults to 0).
25510      * @return {Number} The matched index or -1
25511      */
25512     findIndexBy : function(fn, scope, start){
25513         var me = this,
25514             keys = me.keys,
25515             items = me.items,
25516             i = start || 0,
25517             len = items.length;
25518
25519         for (; i < len; i++) {
25520             if (fn.call(scope || me, items[i], keys[i])) {
25521                 return i;
25522             }
25523         }
25524         return -1;
25525     },
25526
25527     /**
25528      * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
25529      * and by Ext.data.Store#filter
25530      * @private
25531      * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
25532      * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
25533      * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
25534      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
25535      */
25536     createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
25537         if (!value.exec) { // not a regex
25538             var er = Ext.String.escapeRegex;
25539             value = String(value);
25540
25541             if (anyMatch === true) {
25542                 value = er(value);
25543             } else {
25544                 value = '^' + er(value);
25545                 if (exactMatch === true) {
25546                     value += '$';
25547                 }
25548             }
25549             value = new RegExp(value, caseSensitive ? '' : 'i');
25550         }
25551         return value;
25552     },
25553
25554     /**
25555      * Creates a shallow copy of this collection
25556      * @return {MixedCollection}
25557      */
25558     clone : function() {
25559         var me = this,
25560             copy = new this.self(),
25561             keys = me.keys,
25562             items = me.items,
25563             i = 0,
25564             len = items.length;
25565
25566         for(; i < len; i++){
25567             copy.add(keys[i], items[i]);
25568         }
25569         copy.getKey = me.getKey;
25570         return copy;
25571     }
25572 });
25573
25574 /**
25575  * @class Ext.util.Sortable
25576
25577 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}.
25578
25579 **NOTE**: This mixin is mainly for internal library use and most users should not need to use it directly. It
25580 is more likely you will want to use one of the component classes that import this mixin, such as
25581 {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
25582  * @markdown
25583  * @docauthor Tommy Maintz <tommy@sencha.com>
25584  */
25585 Ext.define("Ext.util.Sortable", {
25586     /**
25587      * @property isSortable
25588      * @type Boolean
25589      * Flag denoting that this object is sortable. Always true.
25590      */
25591     isSortable: true,
25592     
25593     /**
25594      * The default sort direction to use if one is not specified (defaults to "ASC")
25595      * @property defaultSortDirection
25596      * @type String
25597      */
25598     defaultSortDirection: "ASC",
25599     
25600     requires: [
25601         'Ext.util.Sorter'
25602     ],
25603
25604     /**
25605      * The property in each item that contains the data to sort. (defaults to null)
25606      * @type String
25607      */    
25608     sortRoot: null,
25609     
25610     /**
25611      * Performs initialization of this mixin. Component classes using this mixin should call this method
25612      * during their own initialization.
25613      */
25614     initSortable: function() {
25615         var me = this,
25616             sorters = me.sorters;
25617         
25618         /**
25619          * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
25620          * @property sorters
25621          * @type Ext.util.MixedCollection
25622          */
25623         me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
25624             return item.id || item.property;
25625         });
25626         
25627         if (sorters) {
25628             me.sorters.addAll(me.decodeSorters(sorters));
25629         }
25630     },
25631
25632     /**
25633      * <p>Sorts the data in the Store by one or more of its properties. Example usage:</p>
25634 <pre><code>
25635 //sort by a single field
25636 myStore.sort('myField', 'DESC');
25637
25638 //sorting by multiple fields
25639 myStore.sort([
25640     {
25641         property : 'age',
25642         direction: 'ASC'
25643     },
25644     {
25645         property : 'name',
25646         direction: 'DESC'
25647     }
25648 ]);
25649 </code></pre>
25650      * <p>Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates the actual
25651      * sorting to its internal {@link Ext.util.MixedCollection}.</p>
25652      * <p>When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:</p>
25653 <pre><code>
25654 store.sort('myField');
25655 store.sort('myField');
25656      </code></pre>
25657      * <p>Is equivalent to this code, because Store handles the toggling automatically:</p>
25658 <pre><code>
25659 store.sort('myField', 'ASC');
25660 store.sort('myField', 'DESC');
25661 </code></pre>
25662      * @param {String|Array} sorters Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
25663      * or an Array of sorter configurations.
25664      * @param {String} direction The overall direction to sort the data by. Defaults to "ASC".
25665      */
25666     sort: function(sorters, direction, where, doSort) {
25667         var me = this,
25668             sorter, sorterFn,
25669             newSorters;
25670         
25671         if (Ext.isArray(sorters)) {
25672             doSort = where;
25673             where = direction;
25674             newSorters = sorters;
25675         }
25676         else if (Ext.isObject(sorters)) {
25677             doSort = where;
25678             where = direction;
25679             newSorters = [sorters];
25680         }
25681         else if (Ext.isString(sorters)) {
25682             sorter = me.sorters.get(sorters);
25683
25684             if (!sorter) {
25685                 sorter = {
25686                     property : sorters,
25687                     direction: direction
25688                 };
25689                 newSorters = [sorter];
25690             }
25691             else if (direction === undefined) {
25692                 sorter.toggle();
25693             }
25694             else {
25695                 sorter.setDirection(direction);
25696             }
25697         }
25698         
25699         if (newSorters && newSorters.length) {
25700             newSorters = me.decodeSorters(newSorters);
25701             if (Ext.isString(where)) {
25702                 if (where === 'prepend') {
25703                     sorters = me.sorters.clone().items;
25704                     
25705                     me.sorters.clear();
25706                     me.sorters.addAll(newSorters);
25707                     me.sorters.addAll(sorters);
25708                 }
25709                 else {
25710                     me.sorters.addAll(newSorters);
25711                 }
25712             }
25713             else {
25714                 me.sorters.clear();
25715                 me.sorters.addAll(newSorters);
25716             }
25717             
25718             if (doSort !== false) {
25719                 me.onBeforeSort(newSorters);
25720             }
25721         }
25722         
25723         if (doSort !== false) {
25724             sorters = me.sorters.items;
25725             if (sorters.length) {
25726                 //construct an amalgamated sorter function which combines all of the Sorters passed
25727                 sorterFn = function(r1, r2) {
25728                     var result = sorters[0].sort(r1, r2),
25729                         length = sorters.length,
25730                         i;
25731
25732                         //if we have more than one sorter, OR any additional sorter functions together
25733                         for (i = 1; i < length; i++) {
25734                             result = result || sorters[i].sort.call(this, r1, r2);
25735                         }
25736
25737                     return result;
25738                 };
25739
25740                 me.doSort(sorterFn);                
25741             }
25742         }
25743         
25744         return sorters;
25745     },
25746     
25747     onBeforeSort: Ext.emptyFn,
25748         
25749     /**
25750      * @private
25751      * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
25752      * @param {Array} sorters The sorters array
25753      * @return {Array} Array of Ext.util.Sorter objects
25754      */
25755     decodeSorters: function(sorters) {
25756         if (!Ext.isArray(sorters)) {
25757             if (sorters === undefined) {
25758                 sorters = [];
25759             } else {
25760                 sorters = [sorters];
25761             }
25762         }
25763
25764         var length = sorters.length,
25765             Sorter = Ext.util.Sorter,
25766             fields = this.model ? this.model.prototype.fields : null,
25767             field,
25768             config, i;
25769
25770         for (i = 0; i < length; i++) {
25771             config = sorters[i];
25772
25773             if (!(config instanceof Sorter)) {
25774                 if (Ext.isString(config)) {
25775                     config = {
25776                         property: config
25777                     };
25778                 }
25779                 
25780                 Ext.applyIf(config, {
25781                     root     : this.sortRoot,
25782                     direction: "ASC"
25783                 });
25784
25785                 //support for 3.x style sorters where a function can be defined as 'fn'
25786                 if (config.fn) {
25787                     config.sorterFn = config.fn;
25788                 }
25789
25790                 //support a function to be passed as a sorter definition
25791                 if (typeof config == 'function') {
25792                     config = {
25793                         sorterFn: config
25794                     };
25795                 }
25796
25797                 // ensure sortType gets pushed on if necessary
25798                 if (fields && !config.transform) {
25799                     field = fields.get(config.property);
25800                     config.transform = field ? field.sortType : undefined;
25801                 }
25802                 sorters[i] = Ext.create('Ext.util.Sorter', config);
25803             }
25804         }
25805
25806         return sorters;
25807     },
25808     
25809     getSorters: function() {
25810         return this.sorters.items;
25811     }
25812 });
25813 /**
25814  * @class Ext.util.MixedCollection
25815  * <p>
25816  * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
25817  * must be unique, the same key cannot exist twice. This collection is ordered, items in the
25818  * collection can be accessed by index  or via the key. Newly added items are added to
25819  * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
25820  * is heavier and provides more functionality. Sample usage:
25821  * <pre><code>
25822 var coll = new Ext.util.MixedCollection();
25823 coll.add('key1', 'val1');
25824 coll.add('key2', 'val2');
25825 coll.add('key3', 'val3');
25826
25827 console.log(coll.get('key1')); // prints 'val1'
25828 console.log(coll.indexOfKey('key3')); // prints 2
25829  * </code></pre>
25830  *
25831  * <p>
25832  * The MixedCollection also has support for sorting and filtering of the values in the collection.
25833  * <pre><code>
25834 var coll = new Ext.util.MixedCollection();
25835 coll.add('key1', 100);
25836 coll.add('key2', -100);
25837 coll.add('key3', 17);
25838 coll.add('key4', 0);
25839 var biggerThanZero = coll.filterBy(function(value){
25840     return value > 0;
25841 });
25842 console.log(biggerThanZero.getCount()); // prints 2
25843  * </code></pre>
25844  * </p>
25845  *
25846  * @constructor
25847  * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
25848  * function should add function references to the collection. Defaults to
25849  * <tt>false</tt>.
25850  * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
25851  * and return the key value for that item.  This is used when available to look up the key on items that
25852  * were passed without an explicit key parameter to a MixedCollection method.  Passing this parameter is
25853  * equivalent to providing an implementation for the {@link #getKey} method.
25854  */
25855 Ext.define('Ext.util.MixedCollection', {
25856     extend: 'Ext.util.AbstractMixedCollection',
25857     mixins: {
25858         sortable: 'Ext.util.Sortable'
25859     },
25860
25861     constructor: function() {
25862         var me = this;
25863         me.callParent(arguments);
25864         me.addEvents('sort');
25865         me.mixins.sortable.initSortable.call(me);
25866     },
25867
25868     doSort: function(sorterFn) {
25869         this.sortBy(sorterFn);
25870     },
25871
25872     /**
25873      * @private
25874      * Performs the actual sorting based on a direction and a sorting function. Internally,
25875      * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
25876      * the sorted array data back into this.items and this.keys
25877      * @param {String} property Property to sort by ('key', 'value', or 'index')
25878      * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
25879      * @param {Function} fn (optional) Comparison function that defines the sort order.
25880      * Defaults to sorting by numeric value.
25881      */
25882     _sort : function(property, dir, fn){
25883         var me = this,
25884             i, len,
25885             dsc   = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
25886
25887             //this is a temporary array used to apply the sorting function
25888             c     = [],
25889             keys  = me.keys,
25890             items = me.items;
25891
25892         //default to a simple sorter function if one is not provided
25893         fn = fn || function(a, b) {
25894             return a - b;
25895         };
25896
25897         //copy all the items into a temporary array, which we will sort
25898         for(i = 0, len = items.length; i < len; i++){
25899             c[c.length] = {
25900                 key  : keys[i],
25901                 value: items[i],
25902                 index: i
25903             };
25904         }
25905
25906         //sort the temporary array
25907         Ext.Array.sort(c, function(a, b){
25908             var v = fn(a[property], b[property]) * dsc;
25909             if(v === 0){
25910                 v = (a.index < b.index ? -1 : 1);
25911             }
25912             return v;
25913         });
25914
25915         //copy the temporary array back into the main this.items and this.keys objects
25916         for(i = 0, len = c.length; i < len; i++){
25917             items[i] = c[i].value;
25918             keys[i]  = c[i].key;
25919         }
25920
25921         me.fireEvent('sort', me);
25922     },
25923
25924     /**
25925      * Sorts the collection by a single sorter function
25926      * @param {Function} sorterFn The function to sort by
25927      */
25928     sortBy: function(sorterFn) {
25929         var me     = this,
25930             items  = me.items,
25931             keys   = me.keys,
25932             length = items.length,
25933             temp   = [],
25934             i;
25935
25936         //first we create a copy of the items array so that we can sort it
25937         for (i = 0; i < length; i++) {
25938             temp[i] = {
25939                 key  : keys[i],
25940                 value: items[i],
25941                 index: i
25942             };
25943         }
25944
25945         Ext.Array.sort(temp, function(a, b) {
25946             var v = sorterFn(a.value, b.value);
25947             if (v === 0) {
25948                 v = (a.index < b.index ? -1 : 1);
25949             }
25950
25951             return v;
25952         });
25953
25954         //copy the temporary array back into the main this.items and this.keys objects
25955         for (i = 0; i < length; i++) {
25956             items[i] = temp[i].value;
25957             keys[i]  = temp[i].key;
25958         }
25959         
25960         me.fireEvent('sort', me, items, keys);
25961     },
25962
25963     /**
25964      * Reorders each of the items based on a mapping from old index to new index. Internally this
25965      * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
25966      * @param {Object} mapping Mapping from old item index to new item index
25967      */
25968     reorder: function(mapping) {
25969         var me = this,
25970             items = me.items,
25971             index = 0,
25972             length = items.length,
25973             order = [],
25974             remaining = [],
25975             oldIndex;
25976
25977         me.suspendEvents();
25978
25979         //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
25980         for (oldIndex in mapping) {
25981             order[mapping[oldIndex]] = items[oldIndex];
25982         }
25983
25984         for (index = 0; index < length; index++) {
25985             if (mapping[index] == undefined) {
25986                 remaining.push(items[index]);
25987             }
25988         }
25989
25990         for (index = 0; index < length; index++) {
25991             if (order[index] == undefined) {
25992                 order[index] = remaining.shift();
25993             }
25994         }
25995
25996         me.clear();
25997         me.addAll(order);
25998
25999         me.resumeEvents();
26000         me.fireEvent('sort', me);
26001     },
26002
26003     /**
26004      * Sorts this collection by <b>key</b>s.
26005      * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
26006      * @param {Function} fn (optional) Comparison function that defines the sort order.
26007      * Defaults to sorting by case insensitive string.
26008      */
26009     sortByKey : function(dir, fn){
26010         this._sort('key', dir, fn || function(a, b){
26011             var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
26012             return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
26013         });
26014     }
26015 });
26016
26017 /**
26018  * @class Ext.data.StoreManager
26019  * @extends Ext.util.MixedCollection
26020  * <p>Contains a collection of all stores that are created that have an identifier.
26021  * An identifier can be assigned by setting the {@link Ext.data.AbstractStore#storeId storeId} 
26022  * property. When a store is in the StoreManager, it can be referred to via it's identifier:
26023  * <pre><code>
26024 Ext.create('Ext.data.Store', {
26025     model: 'SomeModel',
26026     storeId: 'myStore'
26027 });
26028
26029 var store = Ext.data.StoreManager.lookup('myStore');
26030  * </code></pre>
26031  * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.</p>
26032  * <p>
26033  * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when
26034  * registering it with any Component that consumes data from a store:
26035  * <pre><code>
26036 Ext.create('Ext.data.Store', {
26037     model: 'SomeModel',
26038     storeId: 'myStore'
26039 });
26040
26041 Ext.create('Ext.view.View', {
26042     store: 'myStore',
26043     // other configuration here
26044 });
26045  * </code></pre>
26046  * </p>
26047  * @singleton
26048  * @docauthor Evan Trimboli <evan@sencha.com>
26049  * TODO: Make this an AbstractMgr
26050  */
26051 Ext.define('Ext.data.StoreManager', {
26052     extend: 'Ext.util.MixedCollection',
26053     alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
26054     singleton: true,
26055     uses: ['Ext.data.ArrayStore'],
26056     
26057     /**
26058      * @cfg {Object} listeners @hide
26059      */
26060
26061     /**
26062      * Registers one or more Stores with the StoreManager. You do not normally need to register stores
26063      * manually.  Any store initialized with a {@link Ext.data.Store#storeId} will be auto-registered. 
26064      * @param {Ext.data.Store} store1 A Store instance
26065      * @param {Ext.data.Store} store2 (optional)
26066      * @param {Ext.data.Store} etc... (optional)
26067      */
26068     register : function() {
26069         for (var i = 0, s; (s = arguments[i]); i++) {
26070             this.add(s);
26071         }
26072     },
26073
26074     /**
26075      * Unregisters one or more Stores with the StoreManager
26076      * @param {String/Object} id1 The id of the Store, or a Store instance
26077      * @param {String/Object} id2 (optional)
26078      * @param {String/Object} etc... (optional)
26079      */
26080     unregister : function() {
26081         for (var i = 0, s; (s = arguments[i]); i++) {
26082             this.remove(this.lookup(s));
26083         }
26084     },
26085
26086     /**
26087      * Gets a registered Store by id
26088      * @param {String/Object} id The id of the Store, or a Store instance, or a store configuration
26089      * @return {Ext.data.Store}
26090      */
26091     lookup : function(store) {
26092         // handle the case when we are given an array or an array of arrays.
26093         if (Ext.isArray(store)) {
26094             var fields = ['field1'], 
26095                 expand = !Ext.isArray(store[0]),
26096                 data = store,
26097                 i,
26098                 len;
26099                 
26100             if(expand){
26101                 data = [];
26102                 for (i = 0, len = store.length; i < len; ++i) {
26103                     data.push([store[i]]);
26104                 }
26105             } else {
26106                 for(i = 2, len = store[0].length; i <= len; ++i){
26107                     fields.push('field' + i);
26108                 }
26109             }
26110             return Ext.create('Ext.data.ArrayStore', {
26111                 data  : data,
26112                 fields: fields,
26113                 autoDestroy: true,
26114                 autoCreated: true,
26115                 expanded: expand
26116             });
26117         }
26118         
26119         if (Ext.isString(store)) {
26120             // store id
26121             return this.get(store);
26122         } else {
26123             // store instance or store config
26124             return Ext.data.AbstractStore.create(store);
26125         }
26126     },
26127
26128     // getKey implementation for MixedCollection
26129     getKey : function(o) {
26130          return o.storeId;
26131     }
26132 }, function() {    
26133     /**
26134      * <p>Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Mananger}. 
26135      * Sample usage:</p>
26136     <pre><code>
26137     Ext.regStore('AllUsers', {
26138         model: 'User'
26139     });
26140
26141     //the store can now easily be used throughout the application
26142     new Ext.List({
26143         store: 'AllUsers',
26144         ... other config
26145     });
26146     </code></pre>
26147      * @param {String} id The id to set on the new store
26148      * @param {Object} config The store config
26149      * @param {Constructor} cls The new Component class.
26150      * @member Ext
26151      * @method regStore
26152      */
26153     Ext.regStore = function(name, config) {
26154         var store;
26155
26156         if (Ext.isObject(name)) {
26157             config = name;
26158         } else {
26159             config.storeId = name;
26160         }
26161
26162         if (config instanceof Ext.data.Store) {
26163             store = config;
26164         } else {
26165             store = Ext.create('Ext.data.Store', config);
26166         }
26167
26168         return Ext.data.StoreManager.register(store);
26169     };
26170
26171     /**
26172      * Gets a registered Store by id (shortcut to {@link #lookup})
26173      * @param {String/Object} id The id of the Store, or a Store instance
26174      * @return {Ext.data.Store}
26175      * @member Ext
26176      * @method getStore
26177      */
26178     Ext.getStore = function(name) {
26179         return Ext.data.StoreManager.lookup(name);
26180     };
26181 });
26182
26183 /**
26184  * @class Ext.LoadMask
26185  * A simple utility class for generically masking elements while loading data.  If the {@link #store}
26186  * config option is specified, the masking will be automatically synchronized with the store's loading
26187  * process and the mask element will be cached for reuse.
26188  * <p>Example usage:</p>
26189  * <pre><code>
26190 // Basic mask:
26191 var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
26192 myMask.show();
26193 </code></pre>
26194
26195  * @constructor
26196  * Create a new LoadMask
26197  * @param {Mixed} el The element, element ID, or DOM node you wish to mask. Also, may be a Component who's element you wish to mask.
26198  * @param {Object} config The config object
26199  */
26200
26201 Ext.define('Ext.LoadMask', {
26202
26203     /* Begin Definitions */
26204
26205     mixins: {
26206         observable: 'Ext.util.Observable'
26207     },
26208
26209     requires: ['Ext.data.StoreManager'],
26210
26211     /* End Definitions */
26212
26213     /**
26214      * @cfg {Ext.data.Store} store
26215      * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
26216      * hidden on either load success, or load fail.
26217      */
26218
26219     /**
26220      * @cfg {String} msg
26221      * The text to display in a centered loading message box (defaults to 'Loading...')
26222      */
26223     msg : 'Loading...',
26224     /**
26225      * @cfg {String} msgCls
26226      * The CSS class to apply to the loading message element (defaults to "x-mask-loading")
26227      */
26228     msgCls : Ext.baseCSSPrefix + 'mask-loading',
26229     
26230     /**
26231      * @cfg {Boolean} useMsg
26232      * Whether or not to use a loading message class or simply mask the bound element.
26233      */
26234     useMsg: true,
26235
26236     /**
26237      * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false)
26238      * @type Boolean
26239      */
26240     disabled: false,
26241
26242     constructor : function(el, config) {
26243         var me = this;
26244
26245         if (el.isComponent) {
26246             me.bindComponent(el);
26247         } else {
26248             me.el = Ext.get(el);
26249         }
26250         Ext.apply(me, config);
26251
26252         me.addEvents('beforeshow', 'show', 'hide');
26253         if (me.store) {
26254             me.bindStore(me.store, true);
26255         }
26256         me.mixins.observable.constructor.call(me, config);
26257     },
26258
26259     bindComponent: function(comp) {
26260         var me = this,
26261             listeners = {
26262                 resize: me.onComponentResize,
26263                 scope: me
26264             };
26265
26266         if (comp.el) {
26267             me.onComponentRender(comp);
26268         } else {
26269             listeners.render = {
26270                 fn: me.onComponentRender,
26271                 scope: me,
26272                 single: true
26273             };
26274         }
26275         me.mon(comp, listeners);
26276     },
26277
26278     /**
26279      * @private
26280      * Called if we were configured with a Component, and that Component was not yet rendered. Collects the element to mask.
26281      */
26282     onComponentRender: function(comp) {
26283         this.el = comp.getContentTarget();
26284     },
26285
26286     /**
26287      * @private
26288      * Called when this LoadMask's Component is resized. The isMasked method also re-centers any displayed message.
26289      */
26290     onComponentResize: function(comp, w, h) {
26291         this.el.isMasked();
26292     },
26293
26294     /**
26295      * Changes the data store bound to this LoadMask.
26296      * @param {Store} store The store to bind to this LoadMask
26297      */
26298     bindStore : function(store, initial) {
26299         var me = this;
26300
26301         if (!initial && me.store) {
26302             me.mun(me.store, {
26303                 scope: me,
26304                 beforeload: me.onBeforeLoad,
26305                 load: me.onLoad,
26306                 exception: me.onLoad
26307             });
26308             if(!store) {
26309                 me.store = null;
26310             }
26311         }
26312         if (store) {
26313             store = Ext.data.StoreManager.lookup(store);
26314             me.mon(store, {
26315                 scope: me,
26316                 beforeload: me.onBeforeLoad,
26317                 load: me.onLoad,
26318                 exception: me.onLoad
26319             });
26320
26321         }
26322         me.store = store;
26323         if (store && store.isLoading()) {
26324             me.onBeforeLoad();
26325         }
26326     },
26327
26328     /**
26329      * Disables the mask to prevent it from being displayed
26330      */
26331     disable : function() {
26332         var me = this;
26333
26334        me.disabled = true;
26335        if (me.loading) {
26336            me.onLoad();
26337        }
26338     },
26339
26340     /**
26341      * Enables the mask so that it can be displayed
26342      */
26343     enable : function() {
26344         this.disabled = false;
26345     },
26346
26347     /**
26348      * Method to determine whether this LoadMask is currently disabled.
26349      * @return {Boolean} the disabled state of this LoadMask.
26350      */
26351     isDisabled : function() {
26352         return this.disabled;
26353     },
26354
26355     // private
26356     onLoad : function() {
26357         var me = this;
26358
26359         me.loading = false;
26360         me.el.unmask();
26361         me.fireEvent('hide', me, me.el, me.store);
26362     },
26363
26364     // private
26365     onBeforeLoad : function() {
26366         var me = this;
26367
26368         if (!me.disabled && !me.loading && me.fireEvent('beforeshow', me, me.el, me.store) !== false) {
26369             if (me.useMsg) {
26370                 me.el.mask(me.msg, me.msgCls, false);
26371             } else {
26372                 me.el.mask();
26373             }
26374             
26375             me.fireEvent('show', me, me.el, me.store);
26376             me.loading = true;
26377         }
26378     },
26379
26380     /**
26381      * Show this LoadMask over the configured Element.
26382      */
26383     show: function() {
26384         this.onBeforeLoad();
26385     },
26386
26387     /**
26388      * Hide this LoadMask.
26389      */
26390     hide: function() {
26391         this.onLoad();
26392     },
26393
26394     // private
26395     destroy : function() {
26396         this.hide();
26397         this.clearListeners();
26398     }
26399 });
26400
26401 /**
26402  * @class Ext.ComponentLoader
26403  * @extends Ext.ElementLoader
26404  * 
26405  * This class is used to load content via Ajax into a {@link Ext.Component}. In general 
26406  * this class will not be instanced directly, rather a loader configuration will be passed to the
26407  * constructor of the {@link Ext.Component}.
26408  * 
26409  * ## HTML Renderer
26410  * By default, the content loaded will be processed as raw html. The response text
26411  * from the request is taken and added to the component. This can be used in
26412  * conjunction with the {@link #scripts} option to execute any inline scripts in
26413  * the resulting content. Using this renderer has the same effect as passing the
26414  * {@link Ext.Component#html} configuration option.
26415  * 
26416  * ## Data Renderer
26417  * This renderer allows content to be added by using JSON data and a {@link Ext.XTemplate}.
26418  * The content received from the response is passed to the {@link Ext.Component#update} method.
26419  * This content is run through the attached {@link Ext.Component#tpl} and the data is added to
26420  * the Component. Using this renderer has the same effect as using the {@link Ext.Component#data}
26421  * configuration in conjunction with a {@link Ext.Component#tpl}.
26422  * 
26423  * ## Component Renderer
26424  * This renderer can only be used with a {@link Ext.Container} and subclasses. It allows for
26425  * Components to be loaded remotely into a Container. The response is expected to be a single/series of
26426  * {@link Ext.Component} configuration objects. When the response is received, the data is decoded
26427  * and then passed to {@link Ext.Container#add}. Using this renderer has the same effect as specifying
26428  * the {@link Ext.Container#items} configuration on a Container. 
26429  * 
26430  * ## Custom Renderer
26431  * A custom function can be passed to handle any other special case, see the {@link #renderer} option.
26432  * 
26433  * ## Example Usage
26434  *     new Ext.Component({
26435  *         tpl: '{firstName} - {lastName}',
26436  *         loader: {
26437  *             url: 'myPage.php',
26438  *             renderer: 'data',
26439  *             params: {
26440  *                 userId: 1
26441  *             }
26442  *         }
26443  *     });
26444  */
26445 Ext.define('Ext.ComponentLoader', {
26446
26447     /* Begin Definitions */
26448     
26449     extend: 'Ext.ElementLoader',
26450
26451     statics: {
26452         Renderer: {
26453             Data: function(loader, response, active){
26454                 var success = true;
26455                 try {
26456                     loader.getTarget().update(Ext.decode(response.responseText));
26457                 } catch (e) {
26458                     success = false;
26459                 }
26460                 return success;
26461             },
26462
26463             Component: function(loader, response, active){
26464                 var success = true,
26465                     target = loader.getTarget(),
26466                     items = [];
26467
26468                 if (!target.isContainer) {
26469                     Ext.Error.raise({
26470                         target: target,
26471                         msg: 'Components can only be loaded into a container'
26472                     });
26473                 }
26474
26475                 try {
26476                     items = Ext.decode(response.responseText);
26477                 } catch (e) {
26478                     success = false;
26479                 }
26480
26481                 if (success) {
26482                     if (active.removeAll) {
26483                         target.removeAll();
26484                     }
26485                     target.add(items);
26486                 }
26487                 return success;
26488             }
26489         }
26490     },
26491
26492     /* End Definitions */
26493
26494     /**
26495      * @cfg {Ext.Component/String} target The target {@link Ext.Component} for the loader. Defaults to <tt>null</tt>.
26496      * If a string is passed it will be looked up via the id.
26497      */
26498     target: null,
26499
26500     /**
26501      * @cfg {Mixed} loadMask True or a {@link Ext.LoadMask} configuration to enable masking during loading. Defaults to <tt>false</tt>.
26502      */
26503     loadMask: false,
26504     
26505     /**
26506      * @cfg {Boolean} scripts True to parse any inline script tags in the response. This only used when using the html
26507      * {@link #renderer}.
26508      */
26509
26510     /**
26511      * @cfg {String/Function} renderer
26512
26513 The type of content that is to be loaded into, which can be one of 3 types:
26514
26515 + **html** : Loads raw html content, see {@link Ext.Component#html}
26516 + **data** : Loads raw html content, see {@link Ext.Component#data}
26517 + **component** : Loads child {Ext.Component} instances. This option is only valid when used with a Container.
26518
26519 Defaults to `html`.
26520
26521 Alternatively, you can pass a function which is called with the following parameters.
26522
26523 + loader - Loader instance
26524 + response - The server response
26525 + active - The active request
26526
26527 The function must return false is loading is not successful. Below is a sample of using a custom renderer:
26528
26529     new Ext.Component({
26530         loader: {
26531             url: 'myPage.php',
26532             renderer: function(loader, response, active) {
26533                 var text = response.responseText;
26534                 loader.getTarget().update('The response is ' + text);
26535                 return true;
26536             }
26537         }
26538     });
26539      * @markdown
26540      */
26541     renderer: 'html',
26542
26543     /**
26544      * Set a {Ext.Component} as the target of this loader. Note that if the target is changed,
26545      * any active requests will be aborted.
26546      * @param {String/Ext.Component} target The component to be the target of this loader. If a string is passed
26547      * it will be looked up via its id.
26548      */
26549     setTarget: function(target){
26550         var me = this;
26551         
26552         if (Ext.isString(target)) {
26553             target = Ext.getCmp(target);
26554         }
26555
26556         if (me.target && me.target != target) {
26557             me.abort();
26558         }
26559         me.target = target;
26560     },
26561     
26562     // inherit docs
26563     removeMask: function(){
26564         this.target.setLoading(false);
26565     },
26566     
26567     /**
26568      * Add the mask on the target
26569      * @private
26570      * @param {Mixed} mask The mask configuration
26571      */
26572     addMask: function(mask){
26573         this.target.setLoading(mask);
26574     },
26575
26576     /**
26577      * Get the target of this loader.
26578      * @return {Ext.Component} target The target, null if none exists.
26579      */
26580     
26581     setOptions: function(active, options){
26582         active.removeAll = Ext.isDefined(options.removeAll) ? options.removeAll : this.removeAll;
26583     },
26584
26585     /**
26586      * Gets the renderer to use
26587      * @private
26588      * @param {String/Function} renderer The renderer to use
26589      * @return {Function} A rendering function to use.
26590      */
26591     getRenderer: function(renderer){
26592         if (Ext.isFunction(renderer)) {
26593             return renderer;
26594         }
26595
26596         var renderers = this.statics().Renderer;
26597         switch (renderer) {
26598             case 'component':
26599                 return renderers.Component;
26600             case 'data':
26601                 return renderers.Data;
26602             default:
26603                 return Ext.ElementLoader.Renderer.Html;
26604         }
26605     }
26606 });
26607
26608 /**
26609  * @class Ext.layout.component.Auto
26610  * @extends Ext.layout.component.Component
26611  * @private
26612  *
26613  * <p>The AutoLayout is the default layout manager delegated by {@link Ext.Component} to
26614  * render any child Elements when no <tt>{@link Ext.Component#layout layout}</tt> is configured.</p>
26615  */
26616
26617 Ext.define('Ext.layout.component.Auto', {
26618
26619     /* Begin Definitions */
26620
26621     alias: 'layout.autocomponent',
26622
26623     extend: 'Ext.layout.component.Component',
26624
26625     /* End Definitions */
26626
26627     type: 'autocomponent',
26628
26629     onLayout : function(width, height) {
26630         this.setTargetSize(width, height);
26631     }
26632 });
26633 /**
26634  * @class Ext.AbstractComponent
26635  * <p>An abstract base class which provides shared methods for Components across the Sencha product line.</p>
26636  * <p>Please refer to sub class's documentation</p>
26637  * @constructor
26638  */
26639
26640 Ext.define('Ext.AbstractComponent', {
26641
26642     /* Begin Definitions */
26643
26644     mixins: {
26645         observable: 'Ext.util.Observable',
26646         animate: 'Ext.util.Animate',
26647         state: 'Ext.state.Stateful'
26648     },
26649
26650     requires: [
26651         'Ext.PluginManager',
26652         'Ext.ComponentManager',
26653         'Ext.core.Element',
26654         'Ext.core.DomHelper',
26655         'Ext.XTemplate',
26656         'Ext.ComponentQuery',
26657         'Ext.LoadMask',
26658         'Ext.ComponentLoader',
26659         'Ext.EventManager',
26660         'Ext.layout.Layout',
26661         'Ext.layout.component.Auto'
26662     ],
26663
26664     // Please remember to add dependencies whenever you use it
26665     // I had to fix these many times already
26666     uses: [
26667         'Ext.ZIndexManager'
26668     ],
26669
26670     statics: {
26671         AUTO_ID: 1000
26672     },
26673
26674     /* End Definitions */
26675
26676     isComponent: true,
26677
26678     getAutoId: function() {
26679         return ++Ext.AbstractComponent.AUTO_ID;
26680     },
26681
26682     /**
26683      * @cfg {String} id
26684      * <p>The <b><u>unique id of this component instance</u></b> (defaults to an {@link #getId auto-assigned id}).</p>
26685      * <p>It should not be necessary to use this configuration except for singleton objects in your application.
26686      * Components created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.</p>
26687      * <p>Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery} which
26688      * provides selector-based searching for Sencha Components analogous to DOM querying. The {@link Ext.container.Container Container}
26689      * class contains {@link Ext.container.Container#down shortcut methods} to query its descendant Components by selector.</p>
26690      * <p>Note that this id will also be used as the element id for the containing HTML element
26691      * that is rendered to the page for this component. This allows you to write id-based CSS
26692      * rules to style the specific instance of this component uniquely, and also to select
26693      * sub-elements using this component's id as the parent.</p>
26694      * <p><b>Note</b>: to avoid complications imposed by a unique <tt>id</tt> also see <code>{@link #itemId}</code>.</p>
26695      * <p><b>Note</b>: to access the container of a Component see <code>{@link #ownerCt}</code>.</p>
26696      */
26697
26698     /**
26699      * @cfg {String} itemId
26700      * <p>An <tt>itemId</tt> can be used as an alternative way to get a reference to a component
26701      * when no object reference is available.  Instead of using an <code>{@link #id}</code> with
26702      * {@link Ext}.{@link Ext#getCmp getCmp}, use <code>itemId</code> with
26703      * {@link Ext.container.Container}.{@link Ext.container.Container#getComponent getComponent} which will retrieve
26704      * <code>itemId</code>'s or <tt>{@link #id}</tt>'s. Since <code>itemId</code>'s are an index to the
26705      * container's internal MixedCollection, the <code>itemId</code> is scoped locally to the container --
26706      * avoiding potential conflicts with {@link Ext.ComponentManager} which requires a <b>unique</b>
26707      * <code>{@link #id}</code>.</p>
26708      * <pre><code>
26709 var c = new Ext.panel.Panel({ //
26710     {@link Ext.Component#height height}: 300,
26711     {@link #renderTo}: document.body,
26712     {@link Ext.container.Container#layout layout}: 'auto',
26713     {@link Ext.container.Container#items items}: [
26714         {
26715             itemId: 'p1',
26716             {@link Ext.panel.Panel#title title}: 'Panel 1',
26717             {@link Ext.Component#height height}: 150
26718         },
26719         {
26720             itemId: 'p2',
26721             {@link Ext.panel.Panel#title title}: 'Panel 2',
26722             {@link Ext.Component#height height}: 150
26723         }
26724     ]
26725 })
26726 p1 = c.{@link Ext.container.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
26727 p2 = p1.{@link #ownerCt}.{@link Ext.container.Container#getComponent getComponent}('p2'); // reference via a sibling
26728      * </code></pre>
26729      * <p>Also see <tt>{@link #id}</tt>, <code>{@link #query}</code>, <code>{@link #down}</code> and <code>{@link #child}</code>.</p>
26730      * <p><b>Note</b>: to access the container of an item see <tt>{@link #ownerCt}</tt>.</p>
26731      */
26732
26733     /**
26734      * This Component's owner {@link Ext.container.Container Container} (defaults to undefined, and is set automatically when
26735      * this Component is added to a Container).  Read-only.
26736      * <p><b>Note</b>: to access items within the Container see <tt>{@link #itemId}</tt>.</p>
26737      * @type Ext.Container
26738      * @property ownerCt
26739      */
26740
26741     /**
26742      * @cfg {Mixed} autoEl
26743      * <p>A tag name or {@link Ext.core.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
26744      * encapsulate this Component.</p>
26745      * <p>You do not normally need to specify this. For the base classes {@link Ext.Component} and {@link Ext.container.Container},
26746      * this defaults to <b><tt>'div'</tt></b>. The more complex Sencha classes use a more complex
26747      * DOM structure specified by their own {@link #renderTpl}s.</p>
26748      * <p>This is intended to allow the developer to create application-specific utility Components encapsulated by
26749      * different DOM elements. Example usage:</p><pre><code>
26750 {
26751     xtype: 'component',
26752     autoEl: {
26753         tag: 'img',
26754         src: 'http://www.example.com/example.jpg'
26755     }
26756 }, {
26757     xtype: 'component',
26758     autoEl: {
26759         tag: 'blockquote',
26760         html: 'autoEl is cool!'
26761     }
26762 }, {
26763     xtype: 'container',
26764     autoEl: 'ul',
26765     cls: 'ux-unordered-list',
26766     items: {
26767         xtype: 'component',
26768         autoEl: 'li',
26769         html: 'First list item'
26770     }
26771 }
26772 </code></pre>
26773      */
26774
26775     /**
26776      * @cfg {Mixed} renderTpl
26777      * <p>An {@link Ext.XTemplate XTemplate} used to create the internal structure inside this Component's
26778      * encapsulating {@link #getEl Element}.</p>
26779      * <p>You do not normally need to specify this. For the base classes {@link Ext.Component}
26780      * and {@link Ext.container.Container}, this defaults to <b><code>null</code></b> which means that they will be initially rendered
26781      * with no internal structure; they render their {@link #getEl Element} empty. The more specialized ExtJS and Touch classes
26782      * which use a more complex DOM structure, provide their own template definitions.</p>
26783      * <p>This is intended to allow the developer to create application-specific utility Components with customized
26784      * internal structure.</p>
26785      * <p>Upon rendering, any created child elements may be automatically imported into object properties using the
26786      * {@link #renderSelectors} option.</p>
26787      */
26788     renderTpl: null,
26789
26790     /**
26791      * @cfg {Object} renderSelectors
26792
26793 An object containing properties specifying {@link Ext.DomQuery DomQuery} selectors which identify child elements
26794 created by the render process.
26795
26796 After the Component's internal structure is rendered according to the {@link #renderTpl}, this object is iterated through,
26797 and the found Elements are added as properties to the Component using the `renderSelector` property name.
26798
26799 For example, a Component which rendered an image, and description into its element might use the following properties
26800 coded into its prototype:
26801
26802     renderTpl: '&lt;img src="{imageUrl}" class="x-image-component-img">&lt;div class="x-image-component-desc">{description}&gt;/div&lt;',
26803
26804     renderSelectors: {
26805         image: 'img.x-image-component-img',
26806         descEl: 'div.x-image-component-desc'
26807     }
26808
26809 After rendering, the Component would have a property <code>image</code> referencing its child `img` Element,
26810 and a property `descEl` referencing the `div` Element which contains the description.
26811
26812      * @markdown
26813      */
26814
26815     /**
26816      * @cfg {Mixed} renderTo
26817      * <p>Specify the id of the element, a DOM element or an existing Element that this component
26818      * will be rendered into.</p><div><ul>
26819      * <li><b>Notes</b> : <ul>
26820      * <div class="sub-desc">Do <u>not</u> use this option if the Component is to be a child item of
26821      * a {@link Ext.container.Container Container}. It is the responsibility of the
26822      * {@link Ext.container.Container Container}'s {@link Ext.container.Container#layout layout manager}
26823      * to render and manage its child items.</div>
26824      * <div class="sub-desc">When using this config, a call to render() is not required.</div>
26825      * </ul></li>
26826      * </ul></div>
26827      * <p>See <code>{@link #render}</code> also.</p>
26828      */
26829
26830     /**
26831      * @cfg {Boolean} frame
26832      * <p>Specify as <code>true</code> to have the Component inject framing elements within the Component at render time to
26833      * provide a graphical rounded frame around the Component content.</p>
26834      * <p>This is only necessary when running on outdated, or non standard-compliant browsers such as Microsoft's Internet Explorer
26835      * prior to version 9 which do not support rounded corners natively.</p>
26836      * <p>The extra space taken up by this framing is available from the read only property {@link #frameSize}.</p>
26837      */
26838
26839     /**
26840      * <p>Read-only property indicating the width of any framing elements which were added within the encapsulating element
26841      * to provide graphical, rounded borders. See the {@link #frame} config.</p>
26842      * <p> This is an object containing the frame width in pixels for all four sides of the Component containing
26843      * the following properties:</p><div class="mdetail-params"><ul>
26844      * <li><code>top</code> The width of the top framing element in pixels.</li>
26845      * <li><code>right</code> The width of the right framing element in pixels.</li>
26846      * <li><code>bottom</code> The width of the bottom framing element in pixels.</li>
26847      * <li><code>left</code> The width of the left framing element in pixels.</li>
26848      * </ul></div>
26849      * @property frameSize
26850      * @type {Object}
26851      */
26852
26853     /**
26854      * @cfg {String/Object} componentLayout
26855      * <p>The sizing and positioning of a Component's internal Elements is the responsibility of
26856      * the Component's layout manager which sizes a Component's internal structure in response to the Component being sized.</p>
26857      * <p>Generally, developers will not use this configuration as all provided Components which need their internal
26858      * elements sizing (Such as {@link Ext.form.field.Base input fields}) come with their own componentLayout managers.</p>
26859      * <p>The {@link Ext.layout.container.Auto default layout manager} will be used on instances of the base Ext.Component class
26860      * which simply sizes the Component's encapsulating element to the height and width specified in the {@link #setSize} method.</p>
26861      */
26862
26863     /**
26864      * @cfg {Mixed} tpl
26865      * An <bold>{@link Ext.Template}</bold>, <bold>{@link Ext.XTemplate}</bold>
26866      * or an array of strings to form an Ext.XTemplate.
26867      * Used in conjunction with the <code>{@link #data}</code> and
26868      * <code>{@link #tplWriteMode}</code> configurations.
26869      */
26870
26871     /**
26872      * @cfg {Mixed} data
26873      * The initial set of data to apply to the <code>{@link #tpl}</code> to
26874      * update the content area of the Component.
26875      */
26876
26877     /**
26878      * @cfg {String} tplWriteMode The Ext.(X)Template method to use when
26879      * updating the content area of the Component. Defaults to <code>'overwrite'</code>
26880      * (see <code>{@link Ext.XTemplate#overwrite}</code>).
26881      */
26882     tplWriteMode: 'overwrite',
26883
26884     /**
26885      * @cfg {String} baseCls
26886      * The base CSS class to apply to this components's element. This will also be prepended to
26887      * elements within this component like Panel's body will get a class x-panel-body. This means
26888      * that if you create a subclass of Panel, and you want it to get all the Panels styling for the
26889      * element and the body, you leave the baseCls x-panel and use componentCls to add specific styling for this
26890      * component.
26891      */
26892     baseCls: Ext.baseCSSPrefix + 'component',
26893
26894     /**
26895      * @cfg {String} componentCls
26896      * CSS Class to be added to a components root level element to give distinction to it
26897      * via styling.
26898      */
26899
26900     /**
26901      * @cfg {String} cls
26902      * An optional extra CSS class that will be added to this component's Element (defaults to '').  This can be
26903      * useful for adding customized styles to the component or any of its children using standard CSS rules.
26904      */
26905
26906     /**
26907      * @cfg {String} overCls
26908      * An optional extra CSS class that will be added to this component's Element when the mouse moves
26909      * over the Element, and removed when the mouse moves out. (defaults to '').  This can be
26910      * useful for adding customized 'active' or 'hover' styles to the component or any of its children using standard CSS rules.
26911      */
26912
26913     /**
26914      * @cfg {String} disabledCls
26915      * CSS class to add when the Component is disabled. Defaults to 'x-item-disabled'.
26916      */
26917     disabledCls: Ext.baseCSSPrefix + 'item-disabled',
26918
26919     /**
26920      * @cfg {String/Array} ui
26921      * A set style for a component. Can be a string or an Array of multiple strings (UIs)
26922      */
26923     ui: 'default',
26924     
26925     /**
26926      * @cfg {Array} uiCls
26927      * An array of of classNames which are currently applied to this component
26928      * @private
26929      */
26930     uiCls: [],
26931     
26932     /**
26933      * @cfg {String} style
26934      * A custom style specification to be applied to this component's Element.  Should be a valid argument to
26935      * {@link Ext.core.Element#applyStyles}.
26936      * <pre><code>
26937         new Ext.panel.Panel({
26938             title: 'Some Title',
26939             renderTo: Ext.getBody(),
26940             width: 400, height: 300,
26941             layout: 'form',
26942             items: [{
26943                 xtype: 'textarea',
26944                 style: {
26945                     width: '95%',
26946                     marginBottom: '10px'
26947                 }
26948             },
26949             new Ext.button.Button({
26950                 text: 'Send',
26951                 minWidth: '100',
26952                 style: {
26953                     marginBottom: '10px'
26954                 }
26955             })
26956             ]
26957         });
26958      </code></pre>
26959      */
26960
26961     /**
26962      * @cfg {Number} width
26963      * The width of this component in pixels.
26964      */
26965
26966     /**
26967      * @cfg {Number} height
26968      * The height of this component in pixels.
26969      */
26970
26971     /**
26972      * @cfg {Number/String} border
26973      * Specifies the border for this component. The border can be a single numeric value to apply to all sides or
26974      * it can be a CSS style specification for each style, for example: '10 5 3 10'.
26975      */
26976
26977     /**
26978      * @cfg {Number/String} padding
26979      * Specifies the padding for this component. The padding can be a single numeric value to apply to all sides or
26980      * it can be a CSS style specification for each style, for example: '10 5 3 10'.
26981      */
26982
26983     /**
26984      * @cfg {Number/String} margin
26985      * Specifies the margin for this component. The margin can be a single numeric value to apply to all sides or
26986      * it can be a CSS style specification for each style, for example: '10 5 3 10'.
26987      */
26988
26989     /**
26990      * @cfg {Boolean} hidden
26991      * Defaults to false.
26992      */
26993     hidden: false,
26994
26995     /**
26996      * @cfg {Boolean} disabled
26997      * Defaults to false.
26998      */
26999     disabled: false,
27000
27001     /**
27002      * @cfg {Boolean} draggable
27003      * Allows the component to be dragged.
27004      */
27005
27006     /**
27007      * Read-only property indicating whether or not the component can be dragged
27008      * @property draggable
27009      * @type {Boolean}
27010      */
27011     draggable: false,
27012
27013     /**
27014      * @cfg {Boolean} floating
27015      * Create the Component as a floating and use absolute positioning.
27016      * Defaults to false.
27017      */
27018     floating: false,
27019
27020     /**
27021      * @cfg {String} hideMode
27022      * A String which specifies how this Component's encapsulating DOM element will be hidden.
27023      * Values may be<div class="mdetail-params"><ul>
27024      * <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
27025      * <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
27026      * <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
27027      * is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
27028      * in a Component having zero dimensions.</li></ul></div>
27029      * Defaults to <code>'display'</code>.
27030      */
27031     hideMode: 'display',
27032
27033     /**
27034      * @cfg {String} contentEl
27035      * <p>Optional. Specify an existing HTML element, or the <code>id</code> of an existing HTML element to use as the content
27036      * for this component.</p>
27037      * <ul>
27038      * <li><b>Description</b> :
27039      * <div class="sub-desc">This config option is used to take an existing HTML element and place it in the layout element
27040      * of a new component (it simply moves the specified DOM element <i>after the Component is rendered</i> to use as the content.</div></li>
27041      * <li><b>Notes</b> :
27042      * <div class="sub-desc">The specified HTML element is appended to the layout element of the component <i>after any configured
27043      * {@link #html HTML} has been inserted</i>, and so the document will not contain this element at the time the {@link #render} event is fired.</div>
27044      * <div class="sub-desc">The specified HTML element used will not participate in any <code><b>{@link Ext.container.Container#layout layout}</b></code>
27045      * scheme that the Component may use. It is just HTML. Layouts operate on child <code><b>{@link Ext.container.Container#items items}</b></code>.</div>
27046      * <div class="sub-desc">Add either the <code>x-hidden</code> or the <code>x-hide-display</code> CSS class to
27047      * prevent a brief flicker of the content before it is rendered to the panel.</div></li>
27048      * </ul>
27049      */
27050
27051     /**
27052      * @cfg {String/Object} html
27053      * An HTML fragment, or a {@link Ext.core.DomHelper DomHelper} specification to use as the layout element
27054      * content (defaults to ''). The HTML content is added after the component is rendered,
27055      * so the document will not contain this HTML at the time the {@link #render} event is fired.
27056      * This content is inserted into the body <i>before</i> any configured {@link #contentEl} is appended.
27057      */
27058
27059     /**
27060      * @cfg {Boolean} styleHtmlContent
27061      * True to automatically style the html inside the content target of this component (body for panels).
27062      * Defaults to false.
27063      */
27064     styleHtmlContent: false,
27065
27066     /**
27067      * @cfg {String} styleHtmlCls
27068      * The class that is added to the content target when you set styleHtmlContent to true.
27069      * Defaults to 'x-html'
27070      */
27071     styleHtmlCls: Ext.baseCSSPrefix + 'html',
27072
27073     /**
27074      * @cfg {Number} minHeight
27075      * <p>The minimum value in pixels which this Component will set its height to.</p>
27076      * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
27077      */
27078     /**
27079      * @cfg {Number} minWidth
27080      * <p>The minimum value in pixels which this Component will set its width to.</p>
27081      * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
27082      */
27083     /**
27084      * @cfg {Number} maxHeight
27085      * <p>The maximum value in pixels which this Component will set its height to.</p>
27086      * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
27087      */
27088     /**
27089      * @cfg {Number} maxWidth
27090      * <p>The maximum value in pixels which this Component will set its width to.</p>
27091      * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
27092      */
27093
27094     /**
27095      * @cfg {Ext.ComponentLoader/Object} loader
27096      * A configuration object or an instance of a {@link Ext.ComponentLoader} to load remote
27097      * content for this Component.
27098      */
27099
27100      // @private
27101      allowDomMove: true,
27102
27103      /**
27104       * @cfg {Boolean} autoShow True to automatically show the component upon creation.
27105       * This config option may only be used for {@link #floating} components or components
27106       * that use {@link #autoRender}. Defaults to <tt>false</tt>.
27107       */
27108      autoShow: false,
27109
27110     /**
27111      * @cfg {Mixed} autoRender
27112      * <p>This config is intended mainly for {@link #floating} Components which may or may not be shown. Instead
27113      * of using {@link #renderTo} in the configuration, and rendering upon construction, this allows a Component
27114      * to render itself upon first <i>{@link #show}</i>.</p>
27115      * <p>Specify as <code>true</code> to have this Component render to the document body upon first show.</p>
27116      * <p>Specify as an element, or the ID of an element to have this Component render to a specific element upon first show.</p>
27117      * <p><b>This defaults to <code>true</code> for the {@link Ext.window.Window Window} class.</b></p>
27118      */
27119      autoRender: false,
27120
27121      needsLayout: false,
27122
27123     /**
27124      * @cfg {Object/Array} plugins
27125      * An object or array of objects that will provide custom functionality for this component.  The only
27126      * requirement for a valid plugin is that it contain an init method that accepts a reference of type Ext.Component.
27127      * When a component is created, if any plugins are available, the component will call the init method on each
27128      * plugin, passing a reference to itself.  Each plugin can then call methods or respond to events on the
27129      * component as needed to provide its functionality.
27130      */
27131
27132     /**
27133      * Read-only property indicating whether or not the component has been rendered.
27134      * @property rendered
27135      * @type {Boolean}
27136      */
27137     rendered: false,
27138
27139     weight: 0,
27140
27141     trimRe: /^\s+|\s+$/g,
27142     spacesRe: /\s+/,
27143     
27144     
27145     /**
27146      * This is an internal flag that you use when creating custom components.
27147      * By default this is set to true which means that every component gets a mask when its disabled.
27148      * Components like FieldContainer, FieldSet, Field, Button, Tab override this property to false
27149      * since they want to implement custom disable logic.
27150      * @property maskOnDisable
27151      * @type {Boolean}
27152      */     
27153     maskOnDisable: true,
27154
27155     constructor : function(config) {
27156         var me = this,
27157             i, len;
27158
27159         config = config || {};
27160         me.initialConfig = config;
27161         Ext.apply(me, config);
27162
27163         me.addEvents(
27164             /**
27165              * @event beforeactivate
27166              * Fires before a Component has been visually activated.
27167              * Returning false from an event listener can prevent the activate
27168              * from occurring.
27169              * @param {Ext.Component} this
27170              */
27171              'beforeactivate',
27172             /**
27173              * @event activate
27174              * Fires after a Component has been visually activated.
27175              * @param {Ext.Component} this
27176              */
27177              'activate',
27178             /**
27179              * @event beforedeactivate
27180              * Fires before a Component has been visually deactivated.
27181              * Returning false from an event listener can prevent the deactivate
27182              * from occurring.
27183              * @param {Ext.Component} this
27184              */
27185              'beforedeactivate',
27186             /**
27187              * @event deactivate
27188              * Fires after a Component has been visually deactivated.
27189              * @param {Ext.Component} this
27190              */
27191              'deactivate',
27192             /**
27193              * @event added
27194              * Fires after a Component had been added to a Container.
27195              * @param {Ext.Component} this
27196              * @param {Ext.container.Container} container Parent Container
27197              * @param {Number} pos position of Component
27198              */
27199              'added',
27200             /**
27201              * @event disable
27202              * Fires after the component is disabled.
27203              * @param {Ext.Component} this
27204              */
27205              'disable',
27206             /**
27207              * @event enable
27208              * Fires after the component is enabled.
27209              * @param {Ext.Component} this
27210              */
27211              'enable',
27212             /**
27213              * @event beforeshow
27214              * Fires before the component is shown when calling the {@link #show} method.
27215              * Return false from an event handler to stop the show.
27216              * @param {Ext.Component} this
27217              */
27218              'beforeshow',
27219             /**
27220              * @event show
27221              * Fires after the component is shown when calling the {@link #show} method.
27222              * @param {Ext.Component} this
27223              */
27224              'show',
27225             /**
27226              * @event beforehide
27227              * Fires before the component is hidden when calling the {@link #hide} method.
27228              * Return false from an event handler to stop the hide.
27229              * @param {Ext.Component} this
27230              */
27231              'beforehide',
27232             /**
27233              * @event hide
27234              * Fires after the component is hidden.
27235              * Fires after the component is hidden when calling the {@link #hide} method.
27236              * @param {Ext.Component} this
27237              */
27238              'hide',
27239             /**
27240              * @event removed
27241              * Fires when a component is removed from an Ext.container.Container
27242              * @param {Ext.Component} this
27243              * @param {Ext.container.Container} ownerCt Container which holds the component
27244              */
27245              'removed',
27246             /**
27247              * @event beforerender
27248              * Fires before the component is {@link #rendered}. Return false from an
27249              * event handler to stop the {@link #render}.
27250              * @param {Ext.Component} this
27251              */
27252              'beforerender',
27253             /**
27254              * @event render
27255              * Fires after the component markup is {@link #rendered}.
27256              * @param {Ext.Component} this
27257              */
27258              'render',
27259             /**
27260              * @event afterrender
27261              * <p>Fires after the component rendering is finished.</p>
27262              * <p>The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed
27263              * by any afterRender method defined for the Component.</p>
27264              * @param {Ext.Component} this
27265              */
27266              'afterrender',
27267             /**
27268              * @event beforedestroy
27269              * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the {@link #destroy}.
27270              * @param {Ext.Component} this
27271              */
27272              'beforedestroy',
27273             /**
27274              * @event destroy
27275              * Fires after the component is {@link #destroy}ed.
27276              * @param {Ext.Component} this
27277              */
27278              'destroy',
27279             /**
27280              * @event resize
27281              * Fires after the component is resized.
27282              * @param {Ext.Component} this
27283              * @param {Number} adjWidth The box-adjusted width that was set
27284              * @param {Number} adjHeight The box-adjusted height that was set
27285              */
27286              'resize',
27287             /**
27288              * @event move
27289              * Fires after the component is moved.
27290              * @param {Ext.Component} this
27291              * @param {Number} x The new x position
27292              * @param {Number} y The new y position
27293              */
27294              'move'
27295         );
27296
27297         me.getId();
27298
27299         me.mons = [];
27300         me.additionalCls = [];
27301         me.renderData = me.renderData || {};
27302         me.renderSelectors = me.renderSelectors || {};
27303
27304         if (me.plugins) {
27305             me.plugins = [].concat(me.plugins);
27306             for (i = 0, len = me.plugins.length; i < len; i++) {
27307                 me.plugins[i] = me.constructPlugin(me.plugins[i]);
27308             }
27309         }
27310         
27311         me.initComponent();
27312
27313         // ititComponent gets a chance to change the id property before registering
27314         Ext.ComponentManager.register(me);
27315
27316         // Dont pass the config so that it is not applied to 'this' again
27317         me.mixins.observable.constructor.call(me);
27318         me.mixins.state.constructor.call(me, config);
27319
27320         // Move this into Observable?
27321         if (me.plugins) {
27322             me.plugins = [].concat(me.plugins);
27323             for (i = 0, len = me.plugins.length; i < len; i++) {
27324                 me.plugins[i] = me.initPlugin(me.plugins[i]);
27325             }
27326         }
27327
27328         me.loader = me.getLoader();
27329
27330         if (me.renderTo) {
27331             me.render(me.renderTo);
27332             // EXTJSIV-1935 - should be a way to do afterShow or something, but that
27333             // won't work. Likewise, rendering hidden and then showing (w/autoShow) has
27334             // implications to afterRender so we cannot do that.
27335         }
27336
27337         if (me.autoShow) {
27338             me.show();
27339         }
27340         
27341         if (Ext.isDefined(me.disabledClass)) {
27342             if (Ext.isDefined(Ext.global.console)) {
27343                 Ext.global.console.warn('Ext.Component: disabledClass has been deprecated. Please use disabledCls.');
27344             }
27345             me.disabledCls = me.disabledClass;
27346             delete me.disabledClass;
27347         }
27348     },
27349
27350     initComponent: Ext.emptyFn,
27351
27352     show: Ext.emptyFn,
27353
27354     animate: function(animObj) {
27355         var me = this,
27356             to;
27357
27358         animObj = animObj || {};
27359         to = animObj.to || {};
27360
27361         if (Ext.fx.Manager.hasFxBlock(me.id)) {
27362             return me;
27363         }
27364         // Special processing for animating Component dimensions.
27365         if (!animObj.dynamic && (to.height || to.width)) {
27366             var curWidth = me.getWidth(),
27367                 w = curWidth,
27368                 curHeight = me.getHeight(),
27369                 h = curHeight,
27370                 needsResize = false;
27371
27372             if (to.height && to.height > curHeight) {
27373                 h = to.height;
27374                 needsResize = true;
27375             }
27376             if (to.width && to.width > curWidth) {
27377                 w = to.width;
27378                 needsResize = true;
27379             }
27380
27381             // If any dimensions are being increased, we must resize the internal structure
27382             // of the Component, but then clip it by sizing its encapsulating element back to original dimensions.
27383             // The animation will then progressively reveal the larger content.
27384             if (needsResize) {
27385                 var clearWidth = !Ext.isNumber(me.width),
27386                     clearHeight = !Ext.isNumber(me.height);
27387
27388                 me.componentLayout.childrenChanged = true;
27389                 me.setSize(w, h, me.ownerCt);
27390                 me.el.setSize(curWidth, curHeight);
27391                 if (clearWidth) {
27392                     delete me.width;
27393                 }
27394                 if (clearHeight) {
27395                     delete me.height;
27396                 }
27397             }
27398         }
27399         return me.mixins.animate.animate.apply(me, arguments);
27400     },
27401
27402     /**
27403      * <p>This method finds the topmost active layout who's processing will eventually determine the size and position of this
27404      * Component.<p>
27405      * <p>This method is useful when dynamically adding Components into Containers, and some processing must take place after the
27406      * final sizing and positioning of the Component has been performed.</p>
27407      * @returns
27408      */
27409     findLayoutController: function() {
27410         return this.findParentBy(function(c) {
27411             // Return true if we are at the root of the Container tree
27412             // or this Container's layout is busy but the next one up is not.
27413             return !c.ownerCt || (c.layout.layoutBusy && !c.ownerCt.layout.layoutBusy);
27414         });
27415     },
27416
27417     onShow : function() {
27418         // Layout if needed
27419         var needsLayout = this.needsLayout;
27420         if (Ext.isObject(needsLayout)) {
27421             this.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
27422         }
27423     },
27424
27425     constructPlugin: function(plugin) {
27426         if (plugin.ptype && typeof plugin.init != 'function') {
27427             plugin.cmp = this;
27428             plugin = Ext.PluginManager.create(plugin);
27429         }
27430         else if (typeof plugin == 'string') {
27431             plugin = Ext.PluginManager.create({
27432                 ptype: plugin,
27433                 cmp: this
27434             });
27435         }
27436         return plugin;
27437     },
27438
27439     // @private
27440     initPlugin : function(plugin) {
27441         plugin.init(this);
27442
27443         return plugin;
27444     },
27445
27446     /**
27447      * Handles autoRender.
27448      * Floating Components may have an ownerCt. If they are asking to be constrained, constrain them within that
27449      * ownerCt, and have their z-index managed locally. Floating Components are always rendered to document.body
27450      */
27451     doAutoRender: function() {
27452         var me = this;
27453         if (me.floating) {
27454             me.render(document.body);
27455         } else {
27456             me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender);
27457         }
27458     },
27459
27460     // @private
27461     render : function(container, position) {
27462         var me = this;
27463
27464         if (!me.rendered && me.fireEvent('beforerender', me) !== false) {
27465             // If this.el is defined, we want to make sure we are dealing with
27466             // an Ext Element.
27467             if (me.el) {
27468                 me.el = Ext.get(me.el);
27469             }
27470
27471             // Perform render-time processing for floating Components
27472             if (me.floating) {
27473                 me.onFloatRender();
27474             }
27475
27476             container = me.initContainer(container);
27477
27478             me.onRender(container, position);
27479
27480             // Tell the encapsulating element to hide itself in the way the Component is configured to hide
27481             // This means DISPLAY, VISIBILITY or OFFSETS.
27482             me.el.setVisibilityMode(Ext.core.Element[me.hideMode.toUpperCase()]);
27483
27484             if (me.overCls) {
27485                 me.el.hover(me.addOverCls, me.removeOverCls, me);
27486             }
27487
27488             me.fireEvent('render', me);
27489
27490             me.initContent();
27491
27492             me.afterRender(container);
27493             me.fireEvent('afterrender', me);
27494
27495             me.initEvents();
27496
27497             if (me.hidden) {
27498                 // Hiding during the render process should not perform any ancillary
27499                 // actions that the full hide process does; It is not hiding, it begins in a hidden state.'
27500                 // So just make the element hidden according to the configured hideMode
27501                 me.el.hide();
27502             }
27503
27504             if (me.disabled) {
27505                 // pass silent so the event doesn't fire the first time.
27506                 me.disable(true);
27507             }
27508         }
27509         return me;
27510     },
27511
27512     // @private
27513     onRender : function(container, position) {
27514         var me = this,
27515             el = me.el,
27516             cls = me.initCls(),
27517             styles = me.initStyles(),
27518             renderTpl, renderData, i;
27519
27520         position = me.getInsertPosition(position);
27521
27522         if (!el) {
27523             if (position) {
27524                 el = Ext.core.DomHelper.insertBefore(position, me.getElConfig(), true);
27525             }
27526             else {
27527                 el = Ext.core.DomHelper.append(container, me.getElConfig(), true);
27528             }
27529         }
27530         else if (me.allowDomMove !== false) {
27531             if (position) {
27532                 container.dom.insertBefore(el.dom, position);
27533             } else {
27534                 container.dom.appendChild(el.dom);
27535             }
27536         }
27537
27538         if (Ext.scopeResetCSS && !me.ownerCt) {
27539             // If this component's el is the body element, we add the reset class to the html tag
27540             if (el.dom == Ext.getBody().dom) {
27541                 el.parent().addCls(Ext.baseCSSPrefix + 'reset');
27542             }
27543             else {
27544                 // Else we wrap this element in an element that adds the reset class.
27545                 me.resetEl = el.wrap({
27546                     cls: Ext.baseCSSPrefix + 'reset'
27547                 });
27548             }
27549         }
27550
27551         el.addCls(cls);
27552         el.setStyle(styles);
27553
27554         // Here we check if the component has a height set through style or css.
27555         // If it does then we set the this.height to that value and it won't be
27556         // considered an auto height component
27557         // if (this.height === undefined) {
27558         //     var height = el.getHeight();
27559         //     // This hopefully means that the panel has an explicit height set in style or css
27560         //     if (height - el.getPadding('tb') - el.getBorderWidth('tb') > 0) {
27561         //         this.height = height;
27562         //     }
27563         // }
27564
27565         me.el = el;
27566         
27567         me.rendered = true;
27568         me.addUIToElement(true);
27569         //loop through all exisiting uiCls and update the ui in them
27570         for (i = 0; i < me.uiCls.length; i++) {
27571             me.addUIClsToElement(me.uiCls[i], true);
27572         }
27573         me.rendered = false;
27574         me.initFrame();
27575
27576         renderTpl = me.initRenderTpl();
27577         if (renderTpl) {
27578             renderData = me.initRenderData();
27579             renderTpl.append(me.getTargetEl(), renderData);
27580         }
27581
27582         me.applyRenderSelectors();
27583         
27584         me.rendered = true;
27585         
27586         me.setUI(me.ui);
27587     },
27588
27589     // @private
27590     afterRender : function() {
27591         var me = this,
27592             pos,
27593             xy;
27594
27595         me.getComponentLayout();
27596
27597         // Set the size if a size is configured, or if this is the outermost Container
27598         if (!me.ownerCt || (me.height || me.width)) {
27599             me.setSize(me.width, me.height);
27600         }
27601
27602         // For floaters, calculate x and y if they aren't defined by aligning
27603         // the sized element to the center of either the the container or the ownerCt
27604         if (me.floating && (me.x === undefined || me.y === undefined)) {
27605             if (me.floatParent) {
27606                 xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c');
27607                 pos = me.floatParent.getTargetEl().translatePoints(xy[0], xy[1]);
27608             } else {
27609                 xy = me.el.getAlignToXY(me.container, 'c-c');
27610                 pos = me.container.translatePoints(xy[0], xy[1]);
27611             }
27612             me.x = me.x === undefined ? pos.left: me.x;
27613             me.y = me.y === undefined ? pos.top: me.y;
27614         }
27615
27616         if (Ext.isDefined(me.x) || Ext.isDefined(me.y)) {
27617             me.setPosition(me.x, me.y);
27618         }
27619
27620         if (me.styleHtmlContent) {
27621             me.getTargetEl().addCls(me.styleHtmlCls);
27622         }
27623     },
27624
27625     frameCls: Ext.baseCSSPrefix + 'frame',
27626
27627     frameTpl: [
27628         '<tpl if="top">',
27629             '<tpl if="left"><div 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>',
27630                 '<tpl if="right"><div 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>',
27631                     '<div 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>',
27632                 '<tpl if="right"></div></tpl>',
27633             '<tpl if="left"></div></tpl>',
27634         '</tpl>',
27635         '<tpl if="left"><div 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>',
27636             '<tpl if="right"><div 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>',
27637                 '<div class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl></tpl>" role="presentation"></div>',
27638             '<tpl if="right"></div></tpl>',
27639         '<tpl if="left"></div></tpl>',
27640         '<tpl if="bottom">',
27641             '<tpl if="left"><div 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>',
27642                 '<tpl if="right"><div 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>',
27643                     '<div 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>',
27644                 '<tpl if="right"></div></tpl>',
27645             '<tpl if="left"></div></tpl>',
27646         '</tpl>'
27647     ],
27648
27649     frameTableTpl: [
27650         '<table><tbody>',
27651             '<tpl if="top">',
27652                 '<tr>',
27653                     '<tpl if="left"><td 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>',
27654                     '<td 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>',
27655                     '<tpl if="right"><td 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>',
27656                 '</tr>',
27657             '</tpl>',
27658             '<tr>',
27659                 '<tpl if="left"><td 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>',
27660                 '<td 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>',
27661                 '<tpl if="right"><td 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>',
27662             '</tr>',
27663             '<tpl if="bottom">',
27664                 '<tr>',
27665                     '<tpl if="left"><td 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>',
27666                     '<td 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>',
27667                     '<tpl if="right"><td 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>',
27668                 '</tr>',
27669             '</tpl>',
27670         '</tbody></table>'
27671     ],
27672     
27673     /**
27674      * @private
27675      */
27676     initFrame : function() {
27677         if (Ext.supports.CSS3BorderRadius) {
27678             return false;
27679         }
27680         
27681         var me = this,
27682             frameInfo = me.getFrameInfo(),
27683             frameWidth = frameInfo.width,
27684             frameTpl = me.getFrameTpl(frameInfo.table);
27685                         
27686         if (me.frame) {
27687             // Here we render the frameTpl to this component. This inserts the 9point div or the table framing.
27688             frameTpl.insertFirst(me.el, Ext.apply({}, {
27689                 ui:         me.ui,
27690                 uiCls:      me.uiCls,
27691                 frameCls:   me.frameCls,
27692                 baseCls:    me.baseCls,
27693                 frameWidth: frameWidth,
27694                 top:        !!frameInfo.top,
27695                 left:       !!frameInfo.left,
27696                 right:      !!frameInfo.right,
27697                 bottom:     !!frameInfo.bottom
27698             }, me.getFramePositions(frameInfo)));
27699
27700             // The frameBody is returned in getTargetEl, so that layouts render items to the correct target.=
27701             me.frameBody = me.el.down('.' + me.frameCls + '-mc');
27702             
27703             // Add the render selectors for each of the frame elements
27704             Ext.apply(me.renderSelectors, {
27705                 frameTL: '.' + me.baseCls + '-tl',
27706                 frameTC: '.' + me.baseCls + '-tc',
27707                 frameTR: '.' + me.baseCls + '-tr',
27708                 frameML: '.' + me.baseCls + '-ml',
27709                 frameMC: '.' + me.baseCls + '-mc',
27710                 frameMR: '.' + me.baseCls + '-mr',
27711                 frameBL: '.' + me.baseCls + '-bl',
27712                 frameBC: '.' + me.baseCls + '-bc',
27713                 frameBR: '.' + me.baseCls + '-br'
27714             });
27715         }
27716     },
27717     
27718     updateFrame: function() {
27719         if (Ext.supports.CSS3BorderRadius) {
27720             return false;
27721         }
27722         
27723         var me = this,
27724             wasTable = this.frameSize && this.frameSize.table,
27725             oldFrameTL = this.frameTL,
27726             oldFrameBL = this.frameBL,
27727             oldFrameML = this.frameML,
27728             oldFrameMC = this.frameMC,
27729             newMCClassName;
27730         
27731         this.initFrame();
27732         
27733         if (oldFrameMC) {
27734             if (me.frame) {                
27735                 // Reapply render selectors
27736                 delete me.frameTL;
27737                 delete me.frameTC;
27738                 delete me.frameTR;
27739                 delete me.frameML;
27740                 delete me.frameMC;
27741                 delete me.frameMR;
27742                 delete me.frameBL;
27743                 delete me.frameBC;
27744                 delete me.frameBR;    
27745                 this.applyRenderSelectors();
27746                 
27747                 // Store the class names set on the new mc
27748                 newMCClassName = this.frameMC.dom.className;
27749                 
27750                 // Replace the new mc with the old mc
27751                 oldFrameMC.insertAfter(this.frameMC);
27752                 this.frameMC.remove();
27753                 
27754                 // Restore the reference to the old frame mc as the framebody
27755                 this.frameBody = this.frameMC = oldFrameMC;
27756                 
27757                 // Apply the new mc classes to the old mc element
27758                 oldFrameMC.dom.className = newMCClassName;
27759                 
27760                 // Remove the old framing
27761                 if (wasTable) {
27762                     me.el.query('> table')[1].remove();
27763                 }                                
27764                 else {
27765                     if (oldFrameTL) {
27766                         oldFrameTL.remove();
27767                     }
27768                     if (oldFrameBL) {
27769                         oldFrameBL.remove();
27770                     }
27771                     oldFrameML.remove();
27772                 }
27773             }
27774             else {
27775                 // We were framed but not anymore. Move all content from the old frame to the body
27776                 
27777             }
27778         }
27779         else if (me.frame) {
27780             this.applyRenderSelectors();
27781         }
27782     },
27783     
27784     getFrameInfo: function() {
27785         if (Ext.supports.CSS3BorderRadius) {
27786             return false;
27787         }
27788         
27789         var me = this,
27790             left = me.el.getStyle('background-position-x'),
27791             top = me.el.getStyle('background-position-y'),
27792             info, frameInfo = false, max;
27793
27794         // Some browsers dont support background-position-x and y, so for those
27795         // browsers let's split background-position into two parts.
27796         if (!left && !top) {
27797             info = me.el.getStyle('background-position').split(' ');
27798             left = info[0];
27799             top = info[1];
27800         }
27801         
27802         // We actually pass a string in the form of '[type][tl][tr]px [type][br][bl]px' as
27803         // the background position of this.el from the css to indicate to IE that this component needs
27804         // framing. We parse it here and change the markup accordingly.
27805         if (parseInt(left, 10) >= 1000000 && parseInt(top, 10) >= 1000000) {
27806             max = Math.max;
27807             
27808             frameInfo = {
27809                 // Table markup starts with 110, div markup with 100.
27810                 table: left.substr(0, 3) == '110',
27811                 
27812                 // Determine if we are dealing with a horizontal or vertical component
27813                 vertical: top.substr(0, 3) == '110',
27814                 
27815                 // Get and parse the different border radius sizes
27816                 top:    max(left.substr(3, 2), left.substr(5, 2)),
27817                 right:  max(left.substr(5, 2), top.substr(3, 2)),
27818                 bottom: max(top.substr(3, 2), top.substr(5, 2)),
27819                 left:   max(top.substr(5, 2), left.substr(3, 2))
27820             };
27821             
27822             frameInfo.width = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left);
27823
27824             // Just to be sure we set the background image of the el to none.
27825             me.el.setStyle('background-image', 'none');
27826         }        
27827         
27828         // This happens when you set frame: true explicitly without using the x-frame mixin in sass.
27829         // This way IE can't figure out what sizes to use and thus framing can't work.
27830         if (me.frame === true && !frameInfo) {
27831             Ext.Error.raise("You have set frame: true explicity on this component while it doesn't have any " +
27832                             "framing defined in the CSS template. In this case IE can't figure out what sizes " +
27833                             "to use and thus framing on this component will be disabled.");
27834         }
27835         
27836         me.frame = me.frame || !!frameInfo;
27837         me.frameSize = frameInfo || false;
27838         
27839         return frameInfo;
27840     },
27841     
27842     getFramePositions: function(frameInfo) {
27843         var me = this,
27844             frameWidth = frameInfo.width,
27845             dock = me.dock,
27846             positions, tc, bc, ml, mr;
27847             
27848         if (frameInfo.vertical) {
27849             tc = '0 -' + (frameWidth * 0) + 'px';
27850             bc = '0 -' + (frameWidth * 1) + 'px';
27851             
27852             if (dock && dock == "right") {
27853                 tc = 'right -' + (frameWidth * 0) + 'px';
27854                 bc = 'right -' + (frameWidth * 1) + 'px';
27855             }
27856             
27857             positions = {
27858                 tl: '0 -' + (frameWidth * 0) + 'px',
27859                 tr: '0 -' + (frameWidth * 1) + 'px',
27860                 bl: '0 -' + (frameWidth * 2) + 'px',
27861                 br: '0 -' + (frameWidth * 3) + 'px',
27862
27863                 ml: '-' + (frameWidth * 1) + 'px 0',
27864                 mr: 'right 0',
27865
27866                 tc: tc,
27867                 bc: bc
27868             };
27869         } else {
27870             ml = '-' + (frameWidth * 0) + 'px 0';
27871             mr = 'right 0';
27872             
27873             if (dock && dock == "bottom") {
27874                 ml = 'left bottom';
27875                 mr = 'right bottom';
27876             }
27877             
27878             positions = {
27879                 tl: '0 -' + (frameWidth * 2) + 'px',
27880                 tr: 'right -' + (frameWidth * 3) + 'px',
27881                 bl: '0 -' + (frameWidth * 4) + 'px',
27882                 br: 'right -' + (frameWidth * 5) + 'px',
27883
27884                 ml: ml,
27885                 mr: mr,
27886
27887                 tc: '0 -' + (frameWidth * 0) + 'px',
27888                 bc: '0 -' + (frameWidth * 1) + 'px'
27889             };
27890         }
27891         
27892         return positions;
27893     },
27894     
27895     /**
27896      * @private
27897      */
27898     getFrameTpl : function(table) {
27899         return table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl');
27900     },
27901
27902     /**
27903      * <p>Creates an array of class names from the configurations to add to this Component's <code>el</code> on render.</p>
27904      * <p>Private, but (possibly) used by ComponentQuery for selection by class name if Component is not rendered.</p>
27905      * @return {Array} An array of class names with which the Component's element will be rendered.
27906      * @private
27907      */
27908     initCls: function() {
27909         var me = this,
27910             cls = [];
27911
27912         cls.push(me.baseCls);
27913
27914         if (Ext.isDefined(me.cmpCls)) {
27915             if (Ext.isDefined(Ext.global.console)) {
27916                 Ext.global.console.warn('Ext.Component: cmpCls has been deprecated. Please use componentCls.');
27917             }
27918             me.componentCls = me.cmpCls;
27919             delete me.cmpCls;
27920         }
27921
27922         if (me.componentCls) {
27923             cls.push(me.componentCls);
27924         } else {
27925             me.componentCls = me.baseCls;
27926         }
27927         if (me.cls) {
27928             cls.push(me.cls);
27929             delete me.cls;
27930         }
27931
27932         return cls.concat(me.additionalCls);
27933     },
27934     
27935     /**
27936      * Sets the UI for the component. This will remove any existing UIs on the component. It will also
27937      * loop through any uiCls set on the component and rename them so they include the new UI
27938      * @param {String} ui The new UI for the component
27939      */
27940     setUI: function(ui) {
27941         var me = this,
27942             oldUICls = Ext.Array.clone(me.uiCls),
27943             newUICls = [],
27944             cls,
27945             i;
27946         
27947         //loop through all exisiting uiCls and update the ui in them
27948         for (i = 0; i < oldUICls.length; i++) {
27949             cls = oldUICls[i];
27950             
27951             me.removeClsWithUI(cls);
27952             newUICls.push(cls);
27953         }
27954         
27955         //remove the UI from the element
27956         me.removeUIFromElement();
27957         
27958         //set the UI
27959         me.ui = ui;
27960         
27961         //add the new UI to the elemend
27962         me.addUIToElement();
27963         
27964         //loop through all exisiting uiCls and update the ui in them
27965         for (i = 0; i < newUICls.length; i++) {
27966             cls = newUICls[i];
27967             
27968             me.addClsWithUI(cls);
27969         }
27970     },
27971     
27972     /**
27973      * Adds a cls to the uiCls array, which will also call {@link #addUIClsToElement} and adds
27974      * to all elements of this component.
27975      * @param {String/Array} cls A string or an array of strings to add to the uiCls
27976      */
27977     addClsWithUI: function(cls) {
27978         var me = this,
27979             i;
27980         
27981         if (!Ext.isArray(cls)) {
27982             cls = [cls];
27983         }
27984         
27985         for (i = 0; i < cls.length; i++) {
27986             if (cls[i] && !me.hasUICls(cls[i])) {
27987                 me.uiCls = Ext.Array.clone(me.uiCls);
27988                 me.uiCls.push(cls[i]);
27989                 me.addUIClsToElement(cls[i]);
27990             }
27991         }
27992     },
27993     
27994     /**
27995      * Removes a cls to the uiCls array, which will also call {@link #removeUIClsToElement} and removes
27996      * it from all elements of this component.
27997      * @param {String/Array} cls A string or an array of strings to remove to the uiCls
27998      */
27999     removeClsWithUI: function(cls) {
28000         var me = this,
28001             i;
28002         
28003         if (!Ext.isArray(cls)) {
28004             cls = [cls];
28005         }
28006         
28007         for (i = 0; i < cls.length; i++) {
28008             if (cls[i] && me.hasUICls(cls[i])) {
28009                 me.uiCls = Ext.Array.remove(me.uiCls, cls[i]);
28010                 me.removeUIClsFromElement(cls[i]);
28011             }
28012         }
28013     },
28014     
28015     /**
28016      * Checks if there is currently a specified uiCls
28017      * @param {String} cls The cls to check
28018      */
28019     hasUICls: function(cls) {
28020         var me = this,
28021             uiCls = me.uiCls || [];
28022         
28023         return Ext.Array.contains(uiCls, cls);
28024     },
28025     
28026     /**
28027      * Method which adds a specified UI + uiCls to the components element.
28028      * Can be overridden to remove the UI from more than just the components element.
28029      * @param {String} ui The UI to remove from the element
28030      * @private
28031      */
28032     addUIClsToElement: function(cls, force) {
28033         var me = this;
28034         
28035         me.addCls(Ext.baseCSSPrefix + cls);
28036         me.addCls(me.baseCls + '-' + cls);
28037         me.addCls(me.baseCls + '-' + me.ui + '-' + cls);
28038         
28039         if (!force && me.rendered && me.frame && !Ext.supports.CSS3BorderRadius) {
28040             // define each element of the frame
28041             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
28042                 i, el;
28043             
28044             // loop through each of them, and if they are defined add the ui
28045             for (i = 0; i < els.length; i++) {
28046                 el = me['frame' + els[i].toUpperCase()];
28047                 
28048                 if (el && el.dom) {
28049                     el.addCls(me.baseCls + '-' + me.ui + '-' + els[i]);
28050                     el.addCls(me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i]);
28051                 }
28052             }
28053         }
28054     },
28055     
28056     /**
28057      * Method which removes a specified UI + uiCls from the components element.
28058      * The cls which is added to the element will be: `this.baseCls + '-' + ui`
28059      * @param {String} ui The UI to add to the element
28060      * @private
28061      */
28062     removeUIClsFromElement: function(cls, force) {
28063         var me = this;
28064         
28065         me.removeCls(Ext.baseCSSPrefix + cls);
28066         me.removeCls(me.baseCls + '-' + cls);
28067         me.removeCls(me.baseCls + '-' + me.ui + '-' + cls);
28068         
28069         if (!force &&me.rendered && me.frame && !Ext.supports.CSS3BorderRadius) {
28070             // define each element of the frame
28071             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
28072                 i, el;
28073             
28074             // loop through each of them, and if they are defined add the ui
28075             for (i = 0; i < els.length; i++) {
28076                 el = me['frame' + els[i].toUpperCase()];
28077                 if (el && el.dom) {
28078                     el.removeCls(me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i]);
28079                 }
28080             }
28081         }
28082     },
28083     
28084     /**
28085      * Method which adds a specified UI to the components element.
28086      * @private
28087      */
28088     addUIToElement: function(force) {
28089         var me = this;
28090         
28091         me.addCls(me.baseCls + '-' + me.ui);
28092         
28093         if (me.rendered && me.frame && !Ext.supports.CSS3BorderRadius) {
28094             // define each element of the frame
28095             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
28096                 i, el;
28097             
28098             // loop through each of them, and if they are defined add the ui
28099             for (i = 0; i < els.length; i++) {
28100                 el = me['frame' + els[i].toUpperCase()];
28101                 
28102                 if (el) {
28103                     el.addCls(me.baseCls + '-' + me.ui + '-' + els[i]);
28104                 }
28105             }
28106         }
28107     },
28108     
28109     /**
28110      * Method which removes a specified UI from the components element.
28111      * @private
28112      */
28113     removeUIFromElement: function() {
28114         var me = this;
28115         
28116         me.removeCls(me.baseCls + '-' + me.ui);
28117         
28118         if (me.rendered && me.frame && !Ext.supports.CSS3BorderRadius) {
28119             // define each element of the frame
28120             var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
28121                 i, el;
28122             
28123             // loop through each of them, and if they are defined add the ui
28124             for (i = 0; i < els.length; i++) {
28125                 el = me['frame' + els[i].toUpperCase()];
28126                 if (el) {
28127                     el.removeCls(me.baseCls + '-' + me.ui + '-' + els[i]);
28128                 }
28129             }
28130         }
28131     },
28132     
28133     getElConfig : function() {
28134         var result = this.autoEl || {tag: 'div'};
28135         result.id = this.id;
28136         return result;
28137     },
28138
28139     /**
28140      * This function takes the position argument passed to onRender and returns a
28141      * DOM element that you can use in the insertBefore.
28142      * @param {String/Number/Element/HTMLElement} position Index, element id or element you want
28143      * to put this component before.
28144      * @return {HTMLElement} DOM element that you can use in the insertBefore
28145      */
28146     getInsertPosition: function(position) {
28147         // Convert the position to an element to insert before
28148         if (position !== undefined) {
28149             if (Ext.isNumber(position)) {
28150                 position = this.container.dom.childNodes[position];
28151             }
28152             else {
28153                 position = Ext.getDom(position);
28154             }
28155         }
28156
28157         return position;
28158     },
28159
28160     /**
28161      * Adds ctCls to container.
28162      * @return {Ext.core.Element} The initialized container
28163      * @private
28164      */
28165     initContainer: function(container) {
28166         var me = this;
28167
28168         // If you render a component specifying the el, we get the container
28169         // of the el, and make sure we dont move the el around in the dom
28170         // during the render
28171         if (!container && me.el) {
28172             container = me.el.dom.parentNode;
28173             me.allowDomMove = false;
28174         }
28175
28176         me.container = Ext.get(container);
28177
28178         if (me.ctCls) {
28179             me.container.addCls(me.ctCls);
28180         }
28181
28182         return me.container;
28183     },
28184
28185     /**
28186      * Initialized the renderData to be used when rendering the renderTpl.
28187      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
28188      * @private
28189      */
28190     initRenderData: function() {
28191         var me = this;
28192
28193         return Ext.applyIf(me.renderData, {
28194             ui: me.ui,
28195             uiCls: me.uiCls,
28196             baseCls: me.baseCls,
28197             componentCls: me.componentCls,
28198             frame: me.frame
28199         });
28200     },
28201
28202     /**
28203      * @private
28204      */
28205     getTpl: function(name) {
28206         var prototype = this.self.prototype,
28207             ownerPrototype;
28208
28209         if (this.hasOwnProperty(name)) {
28210             if (!(this[name] instanceof Ext.XTemplate)) {
28211                 this[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', this[name]);
28212             }
28213
28214             return this[name];
28215         }
28216
28217         if (!(prototype[name] instanceof Ext.XTemplate)) {
28218             ownerPrototype = prototype;
28219
28220             do {
28221                 if (ownerPrototype.hasOwnProperty(name)) {
28222                     ownerPrototype[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', ownerPrototype[name]);
28223                     break;
28224                 }
28225
28226                 ownerPrototype = ownerPrototype.superclass;
28227             } while (ownerPrototype);
28228         }
28229
28230         return prototype[name];
28231     },
28232
28233     /**
28234      * Initializes the renderTpl.
28235      * @return {Ext.XTemplate} The renderTpl XTemplate instance.
28236      * @private
28237      */
28238     initRenderTpl: function() {
28239         return this.getTpl('renderTpl');
28240     },
28241
28242     /**
28243      * Function description
28244      * @return {String} A CSS style string with style, padding, margin and border.
28245      * @private
28246      */
28247     initStyles: function() {
28248         var style = {},
28249             me = this,
28250             Element = Ext.core.Element;
28251
28252         if (Ext.isString(me.style)) {
28253             style = Element.parseStyles(me.style);
28254         } else {
28255             style = Ext.apply({}, me.style);
28256         }
28257
28258         // Convert the padding, margin and border properties from a space seperated string
28259         // into a proper style string
28260         if (me.padding !== undefined) {
28261             style.padding = Element.unitizeBox((me.padding === true) ? 5 : me.padding);
28262         }
28263
28264         if (me.margin !== undefined) {
28265             style.margin = Element.unitizeBox((me.margin === true) ? 5 : me.margin);
28266         }
28267
28268         delete me.style;
28269         return style;
28270     },
28271
28272     /**
28273      * Initializes this components contents. It checks for the properties
28274      * html, contentEl and tpl/data.
28275      * @private
28276      */
28277     initContent: function() {
28278         var me = this,
28279             target = me.getTargetEl(),
28280             contentEl,
28281             pre;
28282
28283         if (me.html) {
28284             target.update(Ext.core.DomHelper.markup(me.html));
28285             delete me.html;
28286         }
28287
28288         if (me.contentEl) {
28289             contentEl = Ext.get(me.contentEl);
28290             pre = Ext.baseCSSPrefix;
28291             contentEl.removeCls([pre + 'hidden', pre + 'hide-display', pre + 'hide-offsets', pre + 'hide-nosize']);
28292             target.appendChild(contentEl.dom);
28293         }
28294
28295         if (me.tpl) {
28296             // Make sure this.tpl is an instantiated XTemplate
28297             if (!me.tpl.isTemplate) {
28298                 me.tpl = Ext.create('Ext.XTemplate', me.tpl);
28299             }
28300
28301             if (me.data) {
28302                 me.tpl[me.tplWriteMode](target, me.data);
28303                 delete me.data;
28304             }
28305         }
28306     },
28307
28308     // @private
28309     initEvents : function() {
28310         var me = this,
28311             afterRenderEvents = me.afterRenderEvents,
28312             property, listeners;
28313         if (afterRenderEvents) {
28314             for (property in afterRenderEvents) {
28315                 if (afterRenderEvents.hasOwnProperty(property)) {
28316                     listeners = afterRenderEvents[property];
28317                     if (me[property] && me[property].on) {
28318                         me.mon(me[property], listeners);
28319                     }
28320                 }
28321             }
28322         }
28323     },
28324
28325     /**
28326      * Sets references to elements inside the component. E.g body -> x-panel-body
28327      * @private
28328      */
28329     applyRenderSelectors: function() {
28330         var selectors = this.renderSelectors || {},
28331             el = this.el.dom,
28332             selector;
28333
28334         for (selector in selectors) {
28335             if (selectors.hasOwnProperty(selector) && selectors[selector]) {
28336                 this[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], el));
28337             }
28338         }
28339     },
28340
28341     /**
28342      * Tests whether this Component matches the selector string.
28343      * @param {String} selector The selector string to test against.
28344      * @return {Boolean} True if this Component matches the selector.
28345      */
28346     is: function(selector) {
28347         return Ext.ComponentQuery.is(this, selector);
28348     },
28349
28350     /**
28351      * <p>Walks up the <code>ownerCt</code> axis looking for an ancestor Container which matches
28352      * the passed simple selector.</p>
28353      * <p>Example:<pre><code>
28354 var owningTabPanel = grid.up('tabpanel');
28355 </code></pre>
28356      * @param {String} selector Optional. The simple selector to test.
28357      * @return {Container} The matching ancestor Container (or <code>undefined</code> if no match was found).
28358      */
28359     up: function(selector) {
28360         var result = this.ownerCt;
28361         if (selector) {
28362             for (; result; result = result.ownerCt) {
28363                 if (Ext.ComponentQuery.is(result, selector)) {
28364                     return result;
28365                 }
28366             }
28367         }
28368         return result;
28369     },
28370
28371     /**
28372      * <p>Returns the next sibling of this Component.</p>
28373      * <p>Optionally selects the next sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.</p>
28374      * <p>May also be refered to as <code><b>next()</b></code></p>
28375      * <p>Note that this is limited to siblings, and if no siblings of the item match, <code>null</code> is returned. Contrast with {@link #nextNode}</p>
28376      * @param {String} selector Optional A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following items.
28377      * @returns The next sibling (or the next sibling which matches the selector). Returns null if there is no matching sibling.
28378      */
28379     nextSibling: function(selector) {
28380         var o = this.ownerCt, it, last, idx, c;
28381         if (o) {
28382             it = o.items;
28383             idx = it.indexOf(this) + 1;
28384             if (idx) {
28385                 if (selector) {
28386                     for (last = it.getCount(); idx < last; idx++) {
28387                         if ((c = it.getAt(idx)).is(selector)) {
28388                             return c;
28389                         }
28390                     }
28391                 } else {
28392                     if (idx < it.getCount()) {
28393                         return it.getAt(idx);
28394                     }
28395                 }
28396             }
28397         }
28398         return null;
28399     },
28400
28401     /**
28402      * <p>Returns the previous sibling of this Component.</p>
28403      * <p>Optionally selects the previous sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.</p>
28404      * <p>May also be refered to as <code><b>prev()</b></code></p>
28405      * <p>Note that this is limited to siblings, and if no siblings of the item match, <code>null</code> is returned. Contrast with {@link #previousNode}</p>
28406      * @param {String} selector Optional. A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding items.
28407      * @returns The previous sibling (or the previous sibling which matches the selector). Returns null if there is no matching sibling.
28408      */
28409     previousSibling: function(selector) {
28410         var o = this.ownerCt, it, idx, c;
28411         if (o) {
28412             it = o.items;
28413             idx = it.indexOf(this);
28414             if (idx != -1) {
28415                 if (selector) {
28416                     for (--idx; idx >= 0; idx--) {
28417                         if ((c = it.getAt(idx)).is(selector)) {
28418                             return c;
28419                         }
28420                     }
28421                 } else {
28422                     if (idx) {
28423                         return it.getAt(--idx);
28424                     }
28425                 }
28426             }
28427         }
28428         return null;
28429     },
28430
28431     /**
28432      * <p>Returns the previous node in the Component tree in tree traversal order.</p>
28433      * <p>Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will
28434      * walk the tree in reverse order to attempt to find a match. Contrast with {@link #previousSibling}.</p>
28435      * @param {String} selector Optional. A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding nodes.
28436      * @returns The previous node (or the previous node which matches the selector). Returns null if there is no matching node.
28437      */
28438     previousNode: function(selector, includeSelf) {
28439         var node = this,
28440             result,
28441             it, len, i;
28442
28443         // If asked to include self, test me
28444         if (includeSelf && node.is(selector)) {
28445             return node;
28446         }
28447
28448         result = this.prev(selector);
28449         if (result) {
28450             return result;
28451         }
28452
28453         if (node.ownerCt) {
28454             for (it = node.ownerCt.items.items, i = Ext.Array.indexOf(it, node) - 1; i > -1; i--) {
28455                 if (it[i].query) {
28456                     result = it[i].query(selector);
28457                     result = result[result.length - 1];
28458                     if (result) {
28459                         return result;
28460                     }
28461                 }
28462             }
28463             return node.ownerCt.previousNode(selector, true);
28464         }
28465     },
28466
28467     /**
28468      * <p>Returns the next node in the Component tree in tree traversal order.</p>
28469      * <p>Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will
28470      * walk the tree to attempt to find a match. Contrast with {@link #pnextSibling}.</p>
28471      * @param {String} selector Optional A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following nodes.
28472      * @returns The next node (or the next node which matches the selector). Returns null if there is no matching node.
28473      */
28474     nextNode: function(selector, includeSelf) {
28475         var node = this,
28476             result,
28477             it, len, i;
28478
28479         // If asked to include self, test me
28480         if (includeSelf && node.is(selector)) {
28481             return node;
28482         }
28483
28484         result = this.next(selector);
28485         if (result) {
28486             return result;
28487         }
28488
28489         if (node.ownerCt) {
28490             for (it = node.ownerCt.items, i = it.indexOf(node) + 1, it = it.items, len = it.length; i < len; i++) {
28491                 if (it[i].down) {
28492                     result = it[i].down(selector);
28493                     if (result) {
28494                         return result;
28495                     }
28496                 }
28497             }
28498             return node.ownerCt.nextNode(selector);
28499         }
28500     },
28501
28502     /**
28503      * Retrieves the id of this component.
28504      * Will autogenerate an id if one has not already been set.
28505      */
28506     getId : function() {
28507         return this.id || (this.id = 'ext-comp-' + (this.getAutoId()));
28508     },
28509
28510     getItemId : function() {
28511         return this.itemId || this.id;
28512     },
28513
28514     /**
28515      * Retrieves the top level element representing this component.
28516      */
28517     getEl : function() {
28518         return this.el;
28519     },
28520
28521     /**
28522      * This is used to determine where to insert the 'html', 'contentEl' and 'items' in this component.
28523      * @private
28524      */
28525     getTargetEl: function() {
28526         return this.frameBody || this.el;
28527     },
28528
28529     /**
28530      * <p>Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
28531      * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).</p>
28532      * <p><b>If using your own subclasses, be aware that a Component must register its own xtype
28533      * to participate in determination of inherited xtypes.</b></p>
28534      * <p>For a list of all available xtypes, see the {@link Ext.Component} header.</p>
28535      * <p>Example usage:</p>
28536      * <pre><code>
28537 var t = new Ext.form.field.Text();
28538 var isText = t.isXType('textfield');        // true
28539 var isBoxSubclass = t.isXType('field');       // true, descended from Ext.form.field.Base
28540 var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.form.field.Base instance
28541 </code></pre>
28542      * @param {String} xtype The xtype to check for this Component
28543      * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is
28544      * the default), or true to check whether this Component is directly of the specified xtype.
28545      * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
28546      */
28547     isXType: function(xtype, shallow) {
28548         //assume a string by default
28549         if (Ext.isFunction(xtype)) {
28550             xtype = xtype.xtype;
28551             //handle being passed the class, e.g. Ext.Component
28552         } else if (Ext.isObject(xtype)) {
28553             xtype = xtype.statics().xtype;
28554             //handle being passed an instance
28555         }
28556
28557         return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1: this.self.xtype == xtype;
28558     },
28559
28560     /**
28561      * <p>Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all
28562      * available xtypes, see the {@link Ext.Component} header.</p>
28563      * <p><b>If using your own subclasses, be aware that a Component must register its own xtype
28564      * to participate in determination of inherited xtypes.</b></p>
28565      * <p>Example usage:</p>
28566      * <pre><code>
28567 var t = new Ext.form.field.Text();
28568 alert(t.getXTypes());  // alerts 'component/field/textfield'
28569 </code></pre>
28570      * @return {String} The xtype hierarchy string
28571      */
28572     getXTypes: function() {
28573         var self = this.self,
28574             xtypes      = [],
28575             parentPrototype  = this,
28576             xtype;
28577
28578         if (!self.xtypes) {
28579             while (parentPrototype && Ext.getClass(parentPrototype)) {
28580                 xtype = Ext.getClass(parentPrototype).xtype;
28581
28582                 if (xtype !== undefined) {
28583                     xtypes.unshift(xtype);
28584                 }
28585
28586                 parentPrototype = parentPrototype.superclass;
28587             }
28588
28589             self.xtypeChain = xtypes;
28590             self.xtypes = xtypes.join('/');
28591         }
28592
28593         return self.xtypes;
28594     },
28595
28596     /**
28597      * Update the content area of a component.
28598      * @param {Mixed} htmlOrData
28599      * If this component has been configured with a template via the tpl config
28600      * then it will use this argument as data to populate the template.
28601      * If this component was not configured with a template, the components
28602      * content area will be updated via Ext.core.Element update
28603      * @param {Boolean} loadScripts
28604      * (optional) Only legitimate when using the html configuration. Defaults to false
28605      * @param {Function} callback
28606      * (optional) Only legitimate when using the html configuration. Callback to execute when scripts have finished loading
28607      */
28608     update : function(htmlOrData, loadScripts, cb) {
28609         var me = this;
28610
28611         if (me.tpl && !Ext.isString(htmlOrData)) {
28612             me.data = htmlOrData;
28613             if (me.rendered) {
28614                 me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {});
28615             }
28616         } else {
28617             me.html = Ext.isObject(htmlOrData) ? Ext.core.DomHelper.markup(htmlOrData) : htmlOrData;
28618             if (me.rendered) {
28619                 me.getTargetEl().update(me.html, loadScripts, cb);
28620             }
28621         }
28622
28623         if (me.rendered) {
28624             me.doComponentLayout();
28625         }
28626     },
28627
28628     /**
28629      * Convenience function to hide or show this component by boolean.
28630      * @param {Boolean} visible True to show, false to hide
28631      * @return {Ext.Component} this
28632      */
28633     setVisible : function(visible) {
28634         return this[visible ? 'show': 'hide']();
28635     },
28636
28637     /**
28638      * Returns true if this component is visible.
28639      * @param {Boolean} deep. <p>Optional. Pass <code>true</code> to interrogate the visibility status of all
28640      * parent Containers to determine whether this Component is truly visible to the user.</p>
28641      * <p>Generally, to determine whether a Component is hidden, the no argument form is needed. For example
28642      * when creating dynamically laid out UIs in a hidden Container before showing them.</p>
28643      * @return {Boolean} True if this component is visible, false otherwise.
28644      */
28645     isVisible: function(deep) {
28646         var me = this,
28647             child = me,
28648             visible = !me.hidden,
28649             ancestor = me.ownerCt;
28650
28651         // Clear hiddenOwnerCt property
28652         me.hiddenAncestor = false;
28653         if (me.destroyed) {
28654             return false;
28655         }
28656
28657         if (deep && visible && me.rendered && ancestor) {
28658             while (ancestor) {
28659                 // If any ancestor is hidden, then this is hidden.
28660                 // If an ancestor Panel (only Panels have a collapse method) is collapsed,
28661                 // then its layoutTarget (body) is hidden, so this is hidden unless its within a
28662                 // docked item; they are still visible when collapsed (Unless they themseves are hidden)
28663                 if (ancestor.hidden || (ancestor.collapsed &&
28664                         !(ancestor.getDockedItems && Ext.Array.contains(ancestor.getDockedItems(), child)))) {
28665                     // Store hiddenOwnerCt property if needed
28666                     me.hiddenAncestor = ancestor;
28667                     visible = false;
28668                     break;
28669                 }
28670                 child = ancestor;
28671                 ancestor = ancestor.ownerCt;
28672             }
28673         }
28674         return visible;
28675     },
28676
28677     /**
28678      * Enable the component
28679      * @param {Boolean} silent
28680      * Passing false will supress the 'enable' event from being fired.
28681      */
28682     enable: function(silent) {
28683         var me = this;
28684
28685         if (me.rendered) {
28686             me.el.removeCls(me.disabledCls);
28687             me.el.dom.disabled = false;
28688             me.onEnable();
28689         }
28690
28691         me.disabled = false;
28692
28693         if (silent !== true) {
28694             me.fireEvent('enable', me);
28695         }
28696
28697         return me;
28698     },
28699
28700     /**
28701      * Disable the component.
28702      * @param {Boolean} silent
28703      * Passing true, will supress the 'disable' event from being fired.
28704      */
28705     disable: function(silent) {
28706         var me = this;
28707
28708         if (me.rendered) {
28709             me.el.addCls(me.disabledCls);
28710             me.el.dom.disabled = true;
28711             me.onDisable();
28712         }
28713
28714         me.disabled = true;
28715
28716         if (silent !== true) {
28717             me.fireEvent('disable', me);
28718         }
28719
28720         return me;
28721     },
28722     
28723     // @private
28724     onEnable: function() {
28725         if (this.maskOnDisable) {
28726             this.el.unmask();
28727         }        
28728     },
28729
28730     // @private
28731     onDisable : function() {
28732         if (this.maskOnDisable) {
28733             this.el.mask();
28734         }
28735     },
28736     
28737     /**
28738      * Method to determine whether this Component is currently disabled.
28739      * @return {Boolean} the disabled state of this Component.
28740      */
28741     isDisabled : function() {
28742         return this.disabled;
28743     },
28744
28745     /**
28746      * Enable or disable the component.
28747      * @param {Boolean} disabled
28748      */
28749     setDisabled : function(disabled) {
28750         return this[disabled ? 'disable': 'enable']();
28751     },
28752
28753     /**
28754      * Method to determine whether this Component is currently set to hidden.
28755      * @return {Boolean} the hidden state of this Component.
28756      */
28757     isHidden : function() {
28758         return this.hidden;
28759     },
28760
28761     /**
28762      * Adds a CSS class to the top level element representing this component.
28763      * @param {String} cls The CSS class name to add
28764      * @return {Ext.Component} Returns the Component to allow method chaining.
28765      */
28766     addCls : function(className) {
28767         var me = this;
28768         if (!className) {
28769             return me;
28770         }
28771         if (!Ext.isArray(className)){
28772             className = className.replace(me.trimRe, '').split(me.spacesRe);
28773         }
28774         if (me.rendered) {
28775             me.el.addCls(className);
28776         }
28777         else {
28778             me.additionalCls = Ext.Array.unique(me.additionalCls.concat(className));
28779         }
28780         return me;
28781     },
28782
28783     /**
28784      * @deprecated 4.0 Replaced by {@link #addCls}
28785      * Adds a CSS class to the top level element representing this component.
28786      * @param {String} cls The CSS class name to add
28787      * @return {Ext.Component} Returns the Component to allow method chaining.
28788      */
28789     addClass : function() {
28790         return this.addCls.apply(this, arguments);
28791     },
28792
28793     /**
28794      * Removes a CSS class from the top level element representing this component.
28795      * @returns {Ext.Component} Returns the Component to allow method chaining.
28796      */
28797     removeCls : function(className) {
28798         var me = this;
28799
28800         if (!className) {
28801             return me;
28802         }
28803         if (!Ext.isArray(className)){
28804             className = className.replace(me.trimRe, '').split(me.spacesRe);
28805         }
28806         if (me.rendered) {
28807             me.el.removeCls(className);
28808         }
28809         else if (me.additionalCls.length) {
28810             Ext.each(className, function(cls) {
28811                 Ext.Array.remove(me.additionalCls, cls);
28812             });
28813         }
28814         return me;
28815     },
28816
28817     removeClass : function() {
28818         if (Ext.isDefined(Ext.global.console)) {
28819             Ext.global.console.warn('Ext.Component: removeClass has been deprecated. Please use removeCls.');
28820         }
28821         return this.removeCls.apply(this, arguments);
28822     },
28823
28824     addOverCls: function() {
28825         var me = this;
28826         if (!me.disabled) {
28827             me.el.addCls(me.overCls);
28828         }
28829     },
28830
28831     removeOverCls: function() {
28832         this.el.removeCls(this.overCls);
28833     },
28834
28835     addListener : function(element, listeners, scope, options) {
28836         var me = this,
28837             fn,
28838             option;
28839
28840         if (Ext.isString(element) && (Ext.isObject(listeners) || options && options.element)) {
28841             if (options.element) {
28842                 fn = listeners;
28843
28844                 listeners = {};
28845                 listeners[element] = fn;
28846                 element = options.element;
28847                 if (scope) {
28848                     listeners.scope = scope;
28849                 }
28850
28851                 for (option in options) {
28852                     if (options.hasOwnProperty(option)) {
28853                         if (me.eventOptionsRe.test(option)) {
28854                             listeners[option] = options[option];
28855                         }
28856                     }
28857                 }
28858             }
28859
28860             // At this point we have a variable called element,
28861             // and a listeners object that can be passed to on
28862             if (me[element] && me[element].on) {
28863                 me.mon(me[element], listeners);
28864             } else {
28865                 me.afterRenderEvents = me.afterRenderEvents || {};
28866                 me.afterRenderEvents[element] = listeners;
28867             }
28868         }
28869
28870         return me.mixins.observable.addListener.apply(me, arguments);
28871     },
28872     
28873     // inherit docs
28874     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
28875         var me = this,
28876             element = managedListener.options ? managedListener.options.element : null;
28877         
28878         if (element) {
28879             element = me[element];
28880             if (element && element.un) {
28881                 if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
28882                     element.un(managedListener.ename, managedListener.fn, managedListener.scope);
28883                     if (!isClear) {
28884                         Ext.Array.remove(me.managedListeners, managedListener);
28885                     }
28886                 }
28887             }
28888         } else {
28889             return me.mixins.observable.removeManagedListenerItem.apply(me, arguments);
28890         }
28891     },
28892
28893     /**
28894      * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
28895      * @return {Ext.container.Container} the Container which owns this Component.
28896      */
28897     getBubbleTarget : function() {
28898         return this.ownerCt;
28899     },
28900
28901     /**
28902      * Method to determine whether this Component is floating.
28903      * @return {Boolean} the floating state of this component.
28904      */
28905     isFloating : function() {
28906         return this.floating;
28907     },
28908
28909     /**
28910      * Method to determine whether this Component is draggable.
28911      * @return {Boolean} the draggable state of this component.
28912      */
28913     isDraggable : function() {
28914         return !!this.draggable;
28915     },
28916
28917     /**
28918      * Method to determine whether this Component is droppable.
28919      * @return {Boolean} the droppable state of this component.
28920      */
28921     isDroppable : function() {
28922         return !!this.droppable;
28923     },
28924
28925     /**
28926      * @private
28927      * Method to manage awareness of when components are added to their
28928      * respective Container, firing an added event.
28929      * References are established at add time rather than at render time.
28930      * @param {Ext.container.Container} container Container which holds the component
28931      * @param {number} pos Position at which the component was added
28932      */
28933     onAdded : function(container, pos) {
28934         this.ownerCt = container;
28935         this.fireEvent('added', this, container, pos);
28936     },
28937
28938     /**
28939      * @private
28940      * Method to manage awareness of when components are removed from their
28941      * respective Container, firing an removed event. References are properly
28942      * cleaned up after removing a component from its owning container.
28943      */
28944     onRemoved : function() {
28945         var me = this;
28946
28947         me.fireEvent('removed', me, me.ownerCt);
28948         delete me.ownerCt;
28949     },
28950
28951     // @private
28952     beforeDestroy : Ext.emptyFn,
28953     // @private
28954     // @private
28955     onResize : Ext.emptyFn,
28956
28957     /**
28958      * Sets the width and height of this Component. This method fires the {@link #resize} event. This method can accept
28959      * either width and height as separate arguments, or you can pass a size object like <code>{width:10, height:20}</code>.
28960      * @param {Mixed} width The new width to set. This may be one of:<div class="mdetail-params"><ul>
28961      * <li>A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.core.Element#defaultUnit}s (by default, pixels).</li>
28962      * <li>A String used to set the CSS width style.</li>
28963      * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
28964      * <li><code>undefined</code> to leave the width unchanged.</li>
28965      * </ul></div>
28966      * @param {Mixed} height The new height to set (not required if a size object is passed as the first arg).
28967      * This may be one of:<div class="mdetail-params"><ul>
28968      * <li>A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.core.Element#defaultUnit}s (by default, pixels).</li>
28969      * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
28970      * <li><code>undefined</code> to leave the height unchanged.</li>
28971      * </ul></div>
28972      * @return {Ext.Component} this
28973      */
28974     setSize : function(width, height) {
28975         var me = this,
28976             layoutCollection;
28977
28978         // support for standard size objects
28979         if (Ext.isObject(width)) {
28980             height = width.height;
28981             width  = width.width;
28982         }
28983
28984         // Constrain within configured maxima
28985         if (Ext.isNumber(width)) {
28986             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
28987         }
28988         if (Ext.isNumber(height)) {
28989             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
28990         }
28991
28992         if (!me.rendered || !me.isVisible()) {
28993             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
28994             if (me.hiddenAncestor) {
28995                 layoutCollection = me.hiddenAncestor.layoutOnShow;
28996                 layoutCollection.remove(me);
28997                 layoutCollection.add(me);
28998             }
28999             me.needsLayout = {
29000                 width: width,
29001                 height: height,
29002                 isSetSize: true
29003             };
29004             if (!me.rendered) {
29005                 me.width  = (width !== undefined) ? width : me.width;
29006                 me.height = (height !== undefined) ? height : me.height;
29007             }
29008             return me;
29009         }
29010         me.doComponentLayout(width, height, true);
29011
29012         return me;
29013     },
29014
29015     setCalculatedSize : function(width, height, ownerCt) {
29016         var me = this,
29017             layoutCollection;
29018
29019         // support for standard size objects
29020         if (Ext.isObject(width)) {
29021             ownerCt = width.ownerCt;
29022             height = width.height;
29023             width  = width.width;
29024         }
29025
29026         // Constrain within configured maxima
29027         if (Ext.isNumber(width)) {
29028             width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
29029         }
29030         if (Ext.isNumber(height)) {
29031             height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
29032         }
29033
29034         if (!me.rendered || !me.isVisible()) {
29035             // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
29036             if (me.hiddenAncestor) {
29037                 layoutCollection = me.hiddenAncestor.layoutOnShow;
29038                 layoutCollection.remove(me);
29039                 layoutCollection.add(me);
29040             }
29041             me.needsLayout = {
29042                 width: width,
29043                 height: height,
29044                 isSetSize: false,
29045                 ownerCt: ownerCt
29046             };
29047             return me;
29048         }
29049         me.doComponentLayout(width, height, false, ownerCt);
29050
29051         return me;
29052     },
29053
29054     /**
29055      * This method needs to be called whenever you change something on this component that requires the Component's
29056      * layout to be recalculated.
29057      * @return {Ext.container.Container} this
29058      */
29059     doComponentLayout : function(width, height, isSetSize, ownerCt) {
29060         var me = this,
29061             componentLayout = me.getComponentLayout();
29062
29063         // collapsed state is not relevant here, so no testing done.
29064         // Only Panels have a collapse method, and that just sets the width/height such that only
29065         // a single docked Header parallel to the collapseTo side are visible, and the Panel body is hidden.
29066         if (me.rendered && componentLayout) {
29067             width = (width !== undefined) ? width : me.width;
29068             height = (height !== undefined) ? height : me.height;
29069             if (isSetSize) {
29070                 me.width = width;
29071                 me.height = height;
29072             }
29073
29074             componentLayout.layout(width, height, isSetSize, ownerCt);
29075         }
29076         return me;
29077     },
29078
29079     // @private
29080     setComponentLayout : function(layout) {
29081         var currentLayout = this.componentLayout;
29082         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
29083             currentLayout.setOwner(null);
29084         }
29085         this.componentLayout = layout;
29086         layout.setOwner(this);
29087     },
29088
29089     getComponentLayout : function() {
29090         var me = this;
29091
29092         if (!me.componentLayout || !me.componentLayout.isLayout) {
29093             me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
29094         }
29095         return me.componentLayout;
29096     },
29097
29098     /**
29099      * @param {Number} adjWidth The box-adjusted width that was set
29100      * @param {Number} adjHeight The box-adjusted height that was set
29101      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
29102      * @param {Ext.Component} layoutOwner Component which sent the layout. Only used when isSetSize is false.
29103      */
29104     afterComponentLayout: function(width, height, isSetSize, layoutOwner) {
29105         this.fireEvent('resize', this, width, height);
29106     },
29107
29108     /**
29109      * Occurs before componentLayout is run. Returning false from this method will prevent the componentLayout
29110      * from being executed.
29111      * @param {Number} adjWidth The box-adjusted width that was set
29112      * @param {Number} adjHeight The box-adjusted height that was set
29113      * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
29114      * @param {Ext.Component} layoutOwner Component which sent the layout. Only used when isSetSize is false.
29115      */
29116     beforeComponentLayout: function(width, height, isSetSize, layoutOwner) {
29117         return true;
29118     },
29119
29120     /**
29121      * Sets the left and top of the component.  To set the page XY position instead, use {@link #setPagePosition}.
29122      * This method fires the {@link #move} event.
29123      * @param {Number} left The new left
29124      * @param {Number} top The new top
29125      * @return {Ext.Component} this
29126      */
29127     setPosition : function(x, y) {
29128         var me = this;
29129
29130         if (Ext.isObject(x)) {
29131             y = x.y;
29132             x = x.x;
29133         }
29134
29135         if (!me.rendered) {
29136             return me;
29137         }
29138
29139         if (x !== undefined || y !== undefined) {
29140             me.el.setBox(x, y);
29141             me.onPosition(x, y);
29142             me.fireEvent('move', me, x, y);
29143         }
29144         return me;
29145     },
29146
29147     /* @private
29148      * Called after the component is moved, this method is empty by default but can be implemented by any
29149      * subclass that needs to perform custom logic after a move occurs.
29150      * @param {Number} x The new x position
29151      * @param {Number} y The new y position
29152      */
29153     onPosition: Ext.emptyFn,
29154
29155     /**
29156      * Sets the width of the component.  This method fires the {@link #resize} event.
29157      * @param {Number} width The new width to setThis may be one of:<div class="mdetail-params"><ul>
29158      * <li>A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.core.Element#defaultUnit}s (by default, pixels).</li>
29159      * <li>A String used to set the CSS width style.</li>
29160      * </ul></div>
29161      * @return {Ext.Component} this
29162      */
29163     setWidth : function(width) {
29164         return this.setSize(width);
29165     },
29166
29167     /**
29168      * Sets the height of the component.  This method fires the {@link #resize} event.
29169      * @param {Number} height The new height to set. This may be one of:<div class="mdetail-params"><ul>
29170      * <li>A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.core.Element#defaultUnit}s (by default, pixels).</li>
29171      * <li>A String used to set the CSS height style.</li>
29172      * <li><i>undefined</i> to leave the height unchanged.</li>
29173      * </ul></div>
29174      * @return {Ext.Component} this
29175      */
29176     setHeight : function(height) {
29177         return this.setSize(undefined, height);
29178     },
29179
29180     /**
29181      * Gets the current size of the component's underlying element.
29182      * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
29183      */
29184     getSize : function() {
29185         return this.el.getSize();
29186     },
29187
29188     /**
29189      * Gets the current width of the component's underlying element.
29190      * @return {Number}
29191      */
29192     getWidth : function() {
29193         return this.el.getWidth();
29194     },
29195
29196     /**
29197      * Gets the current height of the component's underlying element.
29198      * @return {Number}
29199      */
29200     getHeight : function() {
29201         return this.el.getHeight();
29202     },
29203
29204     /**
29205      * Gets the {@link Ext.ComponentLoader} for this Component.
29206      * @return {Ext.ComponentLoader} The loader instance, null if it doesn't exist.
29207      */
29208     getLoader: function(){
29209         var me = this,
29210             autoLoad = me.autoLoad ? (Ext.isObject(me.autoLoad) ? me.autoLoad : {url: me.autoLoad}) : null,
29211             loader = me.loader || autoLoad;
29212
29213         if (loader) {
29214             if (!loader.isLoader) {
29215                 me.loader = Ext.create('Ext.ComponentLoader', Ext.apply({
29216                     target: me,
29217                     autoLoad: autoLoad
29218                 }, loader));
29219             } else {
29220                 loader.setTarget(me);
29221             }
29222             return me.loader;
29223
29224         }
29225         return null;
29226     },
29227
29228     /**
29229      * This method allows you to show or hide a LoadMask on top of this component.
29230      * @param {Boolean/Object/String} load True to show the default LoadMask, a config object
29231      * that will be passed to the LoadMask constructor, or a message String to show. False to
29232      * hide the current LoadMask.
29233      * @param {Boolean} targetEl True to mask the targetEl of this Component instead of the this.el.
29234      * For example, setting this to true on a Panel will cause only the body to be masked. (defaults to false)
29235      * @return {Ext.LoadMask} The LoadMask instance that has just been shown.
29236      */
29237     setLoading : function(load, targetEl) {
29238         var me = this,
29239             config;
29240
29241         if (me.rendered) {
29242             if (load !== false && !me.collapsed) {
29243                 if (Ext.isObject(load)) {
29244                     config = load;
29245                 }
29246                 else if (Ext.isString(load)) {
29247                     config = {msg: load};
29248                 }
29249                 else {
29250                     config = {};
29251                 }
29252                 me.loadMask = me.loadMask || Ext.create('Ext.LoadMask', targetEl ? me.getTargetEl() : me.el, config);
29253                 me.loadMask.show();
29254             } else if (me.loadMask) {
29255                 Ext.destroy(me.loadMask);
29256                 me.loadMask = null;
29257             }
29258         }
29259
29260         return me.loadMask;
29261     },
29262
29263     /**
29264      * Sets the dock position of this component in its parent panel. Note that
29265      * this only has effect if this item is part of the dockedItems collection
29266      * of a parent that has a DockLayout (note that any Panel has a DockLayout
29267      * by default)
29268      * @return {Component} this
29269      */
29270     setDocked : function(dock, layoutParent) {
29271         var me = this;
29272
29273         me.dock = dock;
29274         if (layoutParent && me.ownerCt && me.rendered) {
29275             me.ownerCt.doComponentLayout();
29276         }
29277         return me;
29278     },
29279
29280     onDestroy : function() {
29281         var me = this;
29282
29283         if (me.monitorResize && Ext.EventManager.resizeEvent) {
29284             Ext.EventManager.resizeEvent.removeListener(me.setSize, me);
29285         }
29286         Ext.destroy(me.componentLayout, me.loadMask);
29287     },
29288
29289     /**
29290      * Destroys the Component.
29291      */
29292     destroy : function() {
29293         var me = this;
29294
29295         if (!me.isDestroyed) {
29296             if (me.fireEvent('beforedestroy', me) !== false) {
29297                 me.destroying = true;
29298                 me.beforeDestroy();
29299
29300                 if (me.floating) {
29301                     delete me.floatParent;
29302                     // A zIndexManager is stamped into a *floating* Component when it is added to a Container.
29303                     // If it has no zIndexManager at render time, it is assigned to the global Ext.WindowManager instance.
29304                     if (me.zIndexManager) {
29305                         me.zIndexManager.unregister(me);
29306                     }
29307                 } else if (me.ownerCt && me.ownerCt.remove) {
29308                     me.ownerCt.remove(me, false);
29309                 }
29310
29311                 if (me.rendered) {
29312                     me.el.remove();
29313                 }
29314
29315                 me.onDestroy();
29316
29317                 // Attempt to destroy all plugins
29318                 Ext.destroy(me.plugins);
29319
29320                 Ext.ComponentManager.unregister(me);
29321                 me.fireEvent('destroy', me);
29322
29323                 me.mixins.state.destroy.call(me);
29324
29325                 me.clearListeners();
29326                 me.destroying = false;
29327                 me.isDestroyed = true;
29328             }
29329         }
29330     },
29331
29332     /**
29333      * Retrieves a plugin by its pluginId which has been bound to this
29334      * component.
29335      * @returns {Ext.AbstractPlugin} pluginInstance
29336      */
29337     getPlugin: function(pluginId) {
29338         var i = 0,
29339             plugins = this.plugins,
29340             ln = plugins.length;
29341         for (; i < ln; i++) {
29342             if (plugins[i].pluginId === pluginId) {
29343                 return plugins[i];
29344             }
29345         }
29346     },
29347     
29348     /**
29349      * Determines whether this component is the descendant of a particular container.
29350      * @param {Ext.Container} container
29351      * @returns {Boolean} isDescendant
29352      */
29353     isDescendantOf: function(container) {
29354         return !!this.findParentBy(function(p){
29355             return p === container;
29356         });
29357     }
29358 }, function() {
29359     this.createAlias({
29360         on: 'addListener',
29361         prev: 'previousSibling',
29362         next: 'nextSibling'
29363     });
29364 });
29365
29366 /**
29367  * @class Ext.AbstractPlugin
29368  * @extends Object
29369  *
29370  * Plugins are injected 
29371  */
29372 Ext.define('Ext.AbstractPlugin', {
29373     disabled: false,
29374     
29375     constructor: function(config) {
29376         if (!config.cmp && Ext.global.console) {
29377             Ext.global.console.warn("Attempted to attach a plugin ");
29378         }
29379         Ext.apply(this, config);
29380     },
29381     
29382     getCmp: function() {
29383         return this.cmp;
29384     },
29385
29386     /**
29387      * The init method is invoked after initComponent has been run for the
29388      * component which we are injecting the plugin into.
29389      * @method
29390      */
29391     init: Ext.emptyFn,
29392
29393     /**
29394      * The destroy method is invoked by the owning Component at the time the Component is being destroyed.
29395      * Use this method to clean up an resources.
29396      * @method
29397      */
29398     destroy: Ext.emptyFn,
29399
29400     /**
29401      * Enable the plugin and set the disabled flag to false.
29402      */
29403     enable: function() {
29404         this.disabled = false;
29405     },
29406
29407     /**
29408      * Disable the plugin and set the disabled flag to true.
29409      */
29410     disable: function() {
29411         this.disabled = true;
29412     }
29413 });
29414
29415 /**
29416  * @class Ext.data.Connection
29417  * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
29418  * to a configured URL, or to a URL specified at request time.
29419  *
29420  * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
29421  * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
29422  * in the request options object, or an {@link #requestcomplete event listener}.
29423  *
29424  * <p><u>File Uploads</u></p>
29425  *
29426  * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
29427  * Instead the form is submitted in the standard manner with the DOM &lt;form&gt; element temporarily modified to have its
29428  * target set to refer to a dynamically generated, hidden &lt;iframe&gt; which is inserted into the document but removed
29429  * after the return data has been gathered.
29430  *
29431  * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
29432  * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
29433  * insert the text unchanged into the document body.
29434  *
29435  * Characters which are significant to an HTML parser must be sent as HTML entities, so encode "&lt;" as "&amp;lt;", "&amp;" as
29436  * "&amp;amp;" etc.
29437  *
29438  * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
29439  * responseText property in order to conform to the requirements of event handlers and callbacks.
29440  *
29441  * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
29442  * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
29443  * packet content.
29444  *
29445  * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
29446  */
29447 Ext.define('Ext.data.Connection', {
29448     mixins: {
29449         observable: 'Ext.util.Observable'
29450     },
29451
29452     statics: {
29453         requestId: 0
29454     },
29455
29456     url: null,
29457     async: true,
29458     method: null,
29459     username: '',
29460     password: '',
29461
29462     /**
29463      * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
29464      */
29465     disableCaching: true,
29466
29467     /**
29468      * @cfg {String} disableCachingParam (Optional) Change the parameter which is sent went disabling caching
29469      * through a cache buster. Defaults to '_dc'
29470      */
29471     disableCachingParam: '_dc',
29472
29473     /**
29474      * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
29475      */
29476     timeout : 30000,
29477
29478     /**
29479      * @cfg {Object} extraParams (Optional) Any parameters to be appended to the request.
29480      */
29481
29482     useDefaultHeader : true,
29483     defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
29484     useDefaultXhrHeader : true,
29485     defaultXhrHeader : 'XMLHttpRequest',
29486
29487     constructor : function(config) {
29488         config = config || {};
29489         Ext.apply(this, config);
29490
29491         this.addEvents(
29492             /**
29493              * @event beforerequest
29494              * Fires before a network request is made to retrieve a data object.
29495              * @param {Connection} conn This Connection object.
29496              * @param {Object} options The options config object passed to the {@link #request} method.
29497              */
29498             'beforerequest',
29499             /**
29500              * @event requestcomplete
29501              * Fires if the request was successfully completed.
29502              * @param {Connection} conn This Connection object.
29503              * @param {Object} response The XHR object containing the response data.
29504              * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
29505              * for details.
29506              * @param {Object} options The options config object passed to the {@link #request} method.
29507              */
29508             'requestcomplete',
29509             /**
29510              * @event requestexception
29511              * Fires if an error HTTP status was returned from the server.
29512              * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP Status Code Definitions</a>
29513              * for details of HTTP status codes.
29514              * @param {Connection} conn This Connection object.
29515              * @param {Object} response The XHR object containing the response data.
29516              * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
29517              * for details.
29518              * @param {Object} options The options config object passed to the {@link #request} method.
29519              */
29520             'requestexception'
29521         );
29522         this.requests = {};
29523         this.mixins.observable.constructor.call(this);
29524     },
29525
29526     /**
29527      * <p>Sends an HTTP request to a remote server.</p>
29528      * <p><b>Important:</b> Ajax server requests are asynchronous, and this call will
29529      * return before the response has been received. Process any returned data
29530      * in a callback function.</p>
29531      * <pre><code>
29532 Ext.Ajax.request({
29533 url: 'ajax_demo/sample.json',
29534 success: function(response, opts) {
29535   var obj = Ext.decode(response.responseText);
29536   console.dir(obj);
29537 },
29538 failure: function(response, opts) {
29539   console.log('server-side failure with status code ' + response.status);
29540 }
29541 });
29542      * </code></pre>
29543      * <p>To execute a callback function in the correct scope, use the <tt>scope</tt> option.</p>
29544      * @param {Object} options An object which may contain the following properties:<ul>
29545      * <li><b>url</b> : String/Function (Optional)<div class="sub-desc">The URL to
29546      * which to send the request, or a function to call which returns a URL string. The scope of the
29547      * function is specified by the <tt>scope</tt> option. Defaults to the configured
29548      * <tt>{@link #url}</tt>.</div></li>
29549      * <li><b>params</b> : Object/String/Function (Optional)<div class="sub-desc">
29550      * An object containing properties which are used as parameters to the
29551      * request, a url encoded string or a function to call to get either. The scope of the function
29552      * is specified by the <tt>scope</tt> option.</div></li>
29553      * <li><b>method</b> : String (Optional)<div class="sub-desc">The HTTP method to use
29554      * for the request. Defaults to the configured method, or if no method was configured,
29555      * "GET" if no parameters are being sent, and "POST" if parameters are being sent.  Note that
29556      * the method name is case-sensitive and should be all caps.</div></li>
29557      * <li><b>callback</b> : Function (Optional)<div class="sub-desc">The
29558      * function to be called upon receipt of the HTTP response. The callback is
29559      * called regardless of success or failure and is passed the following
29560      * parameters:<ul>
29561      * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
29562      * <li><b>success</b> : Boolean<div class="sub-desc">True if the request succeeded.</div></li>
29563      * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.
29564      * See <a href="http://www.w3.org/TR/XMLHttpRequest/">http://www.w3.org/TR/XMLHttpRequest/</a> for details about
29565      * accessing elements of the response.</div></li>
29566      * </ul></div></li>
29567      * <li><a id="request-option-success"></a><b>success</b> : Function (Optional)<div class="sub-desc">The function
29568      * to be called upon success of the request. The callback is passed the following
29569      * parameters:<ul>
29570      * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
29571      * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
29572      * </ul></div></li>
29573      * <li><b>failure</b> : Function (Optional)<div class="sub-desc">The function
29574      * to be called upon failure of the request. The callback is passed the
29575      * following parameters:<ul>
29576      * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
29577      * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
29578      * </ul></div></li>
29579      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
29580      * which to execute the callbacks: The "this" object for the callback function. If the <tt>url</tt>, or <tt>params</tt> options were
29581      * specified as functions from which to draw values, then this also serves as the scope for those function calls.
29582      * Defaults to the browser window.</div></li>
29583      * <li><b>timeout</b> : Number (Optional)<div class="sub-desc">The timeout in milliseconds to be used for this request. Defaults to 30 seconds.</div></li>
29584      * <li><b>form</b> : Element/HTMLElement/String (Optional)<div class="sub-desc">The <tt>&lt;form&gt;</tt>
29585      * Element or the id of the <tt>&lt;form&gt;</tt> to pull parameters from.</div></li>
29586      * <li><a id="request-option-isUpload"></a><b>isUpload</b> : Boolean (Optional)<div class="sub-desc"><b>Only meaningful when used
29587      * with the <tt>form</tt> option</b>.
29588      * <p>True if the form object is a file upload (will be set automatically if the form was
29589      * configured with <b><tt>enctype</tt></b> "multipart/form-data").</p>
29590      * <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
29591      * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
29592      * DOM <tt>&lt;form></tt> element temporarily modified to have its
29593      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
29594      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
29595      * but removed after the return data has been gathered.</p>
29596      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
29597      * server is using JSON to send the return object, then the
29598      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
29599      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
29600      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
29601      * is created containing a <tt>responseText</tt> property in order to conform to the
29602      * requirements of event handlers and callbacks.</p>
29603      * <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>
29604      * and some server technologies (notably JEE) may require some custom processing in order to
29605      * retrieve parameter names and parameter values from the packet content.</p>
29606      * </div></li>
29607      * <li><b>headers</b> : Object (Optional)<div class="sub-desc">Request
29608      * headers to set for the request.</div></li>
29609      * <li><b>xmlData</b> : Object (Optional)<div class="sub-desc">XML document
29610      * to use for the post. Note: This will be used instead of params for the post
29611      * data. Any params will be appended to the URL.</div></li>
29612      * <li><b>jsonData</b> : Object/String (Optional)<div class="sub-desc">JSON
29613      * data to use as the post. Note: This will be used instead of params for the post
29614      * data. Any params will be appended to the URL.</div></li>
29615      * <li><b>disableCaching</b> : Boolean (Optional)<div class="sub-desc">True
29616      * to add a unique cache-buster param to GET requests.</div></li>
29617      * </ul></p>
29618      * <p>The options object may also contain any other property which might be needed to perform
29619      * postprocessing in a callback because it is passed to callback functions.</p>
29620      * @return {Object} request The request object. This may be used
29621      * to cancel the request.
29622      */
29623     request : function(options) {
29624         options = options || {};
29625         var me = this,
29626             scope = options.scope || window,
29627             username = options.username || me.username,
29628             password = options.password || me.password || '',
29629             async,
29630             requestOptions,
29631             request,
29632             headers,
29633             xhr;
29634
29635         if (me.fireEvent('beforerequest', me, options) !== false) {
29636
29637             requestOptions = me.setOptions(options, scope);
29638
29639             if (this.isFormUpload(options) === true) {
29640                 this.upload(options.form, requestOptions.url, requestOptions.data, options);
29641                 return null;
29642             }
29643
29644             // if autoabort is set, cancel the current transactions
29645             if (options.autoAbort === true || me.autoAbort) {
29646                 me.abort();
29647             }
29648
29649             // create a connection object
29650             xhr = this.getXhrInstance();
29651
29652             async = options.async !== false ? (options.async || me.async) : false;
29653
29654             // open the request
29655             if (username) {
29656                 xhr.open(requestOptions.method, requestOptions.url, async, username, password);
29657             } else {
29658                 xhr.open(requestOptions.method, requestOptions.url, async);
29659             }
29660
29661             headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
29662
29663             // create the transaction object
29664             request = {
29665                 id: ++Ext.data.Connection.requestId,
29666                 xhr: xhr,
29667                 headers: headers,
29668                 options: options,
29669                 async: async,
29670                 timeout: setTimeout(function() {
29671                     request.timedout = true;
29672                     me.abort(request);
29673                 }, options.timeout || me.timeout)
29674             };
29675             me.requests[request.id] = request;
29676
29677             // bind our statechange listener
29678             if (async) {
29679                 xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
29680             }
29681
29682             // start the request!
29683             xhr.send(requestOptions.data);
29684             if (!async) {
29685                 return this.onComplete(request);
29686             }
29687             return request;
29688         } else {
29689             Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
29690             return null;
29691         }
29692     },
29693
29694     /**
29695      * Upload a form using a hidden iframe.
29696      * @param {Mixed} form The form to upload
29697      * @param {String} url The url to post to
29698      * @param {String} params Any extra parameters to pass
29699      * @param {Object} options The initial options
29700      */
29701     upload: function(form, url, params, options){
29702         form = Ext.getDom(form);
29703         options = options || {};
29704
29705         var id = Ext.id(),
29706                 frame = document.createElement('iframe'),
29707                 hiddens = [],
29708                 encoding = 'multipart/form-data',
29709                 buf = {
29710                     target: form.target,
29711                     method: form.method,
29712                     encoding: form.encoding,
29713                     enctype: form.enctype,
29714                     action: form.action
29715                 }, hiddenItem;
29716
29717         /*
29718          * Originally this behaviour was modified for Opera 10 to apply the secure URL after
29719          * the frame had been added to the document. It seems this has since been corrected in
29720          * Opera so the behaviour has been reverted, the URL will be set before being added.
29721          */
29722         Ext.fly(frame).set({
29723             id: id,
29724             name: id,
29725             cls: Ext.baseCSSPrefix + 'hide-display',
29726             src: Ext.SSL_SECURE_URL
29727         });
29728
29729         document.body.appendChild(frame);
29730
29731         // This is required so that IE doesn't pop the response up in a new window.
29732         if (document.frames) {
29733            document.frames[id].name = id;
29734         }
29735
29736         Ext.fly(form).set({
29737             target: id,
29738             method: 'POST',
29739             enctype: encoding,
29740             encoding: encoding,
29741             action: url || buf.action
29742         });
29743
29744         // add dynamic params
29745         if (params) {
29746             Ext.iterate(Ext.Object.fromQueryString(params), function(name, value){
29747                 hiddenItem = document.createElement('input');
29748                 Ext.fly(hiddenItem).set({
29749                     type: 'hidden',
29750                     value: value,
29751                     name: name
29752                 });
29753                 form.appendChild(hiddenItem);
29754                 hiddens.push(hiddenItem);
29755             });
29756         }
29757
29758         Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
29759         form.submit();
29760
29761         Ext.fly(form).set(buf);
29762         Ext.each(hiddens, function(h) {
29763             Ext.removeNode(h);
29764         });
29765     },
29766
29767     onUploadComplete: function(frame, options){
29768         var me = this,
29769             // bogus response object
29770             response = {
29771                 responseText: '',
29772                 responseXML: null
29773             }, doc, firstChild;
29774
29775         try {
29776             doc = frame.contentWindow.document || frame.contentDocument || window.frames[id].document;
29777             if (doc) {
29778                 if (doc.body) {
29779                     if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
29780                         response.responseText = firstChild.value;
29781                     } else {
29782                         response.responseText = doc.body.innerHTML;
29783                     }
29784                 }
29785                 //in IE the document may still have a body even if returns XML.
29786                 response.responseXML = doc.XMLDocument || doc;
29787             }
29788         } catch (e) {
29789         }
29790
29791         me.fireEvent('requestcomplete', me, response, options);
29792
29793         Ext.callback(options.success, options.scope, [response, options]);
29794         Ext.callback(options.callback, options.scope, [options, true, response]);
29795
29796         setTimeout(function(){
29797             Ext.removeNode(frame);
29798         }, 100);
29799     },
29800
29801     /**
29802      * Detect whether the form is intended to be used for an upload.
29803      * @private
29804      */
29805     isFormUpload: function(options){
29806         var form = this.getForm(options);
29807         if (form) {
29808             return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
29809         }
29810         return false;
29811     },
29812
29813     /**
29814      * Get the form object from options.
29815      * @private
29816      * @param {Object} options The request options
29817      * @return {HTMLElement} The form, null if not passed
29818      */
29819     getForm: function(options){
29820         return Ext.getDom(options.form) || null;
29821     },
29822
29823     /**
29824      * Set various options such as the url, params for the request
29825      * @param {Object} options The initial options
29826      * @param {Object} scope The scope to execute in
29827      * @return {Object} The params for the request
29828      */
29829     setOptions: function(options, scope){
29830         var me =  this,
29831             params = options.params || {},
29832             extraParams = me.extraParams,
29833             urlParams = options.urlParams,
29834             url = options.url || me.url,
29835             jsonData = options.jsonData,
29836             method,
29837             disableCache,
29838             data;
29839
29840
29841         // allow params to be a method that returns the params object
29842         if (Ext.isFunction(params)) {
29843             params = params.call(scope, options);
29844         }
29845
29846         // allow url to be a method that returns the actual url
29847         if (Ext.isFunction(url)) {
29848             url = url.call(scope, options);
29849         }
29850
29851         url = this.setupUrl(options, url);
29852
29853         if (!url) {
29854             Ext.Error.raise({
29855                 options: options,
29856                 msg: 'No URL specified'
29857             });
29858         }
29859
29860         // check for xml or json data, and make sure json data is encoded
29861         data = options.rawData || options.xmlData || jsonData || null;
29862         if (jsonData && !Ext.isPrimitive(jsonData)) {
29863             data = Ext.encode(data);
29864         }
29865
29866         // make sure params are a url encoded string and include any extraParams if specified
29867         if (Ext.isObject(params)) {
29868             params = Ext.Object.toQueryString(params);
29869         }
29870
29871         if (Ext.isObject(extraParams)) {
29872             extraParams = Ext.Object.toQueryString(extraParams);
29873         }
29874
29875         params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
29876
29877         urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
29878
29879         params = this.setupParams(options, params);
29880
29881         // decide the proper method for this request
29882         method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
29883         this.setupMethod(options, method);
29884
29885
29886         disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
29887         // if the method is get append date to prevent caching
29888         if (method === 'GET' && disableCache) {
29889             url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
29890         }
29891
29892         // if the method is get or there is json/xml data append the params to the url
29893         if ((method == 'GET' || data) && params) {
29894             url = Ext.urlAppend(url, params);
29895             params = null;
29896         }
29897
29898         // allow params to be forced into the url
29899         if (urlParams) {
29900             url = Ext.urlAppend(url, urlParams);
29901         }
29902
29903         return {
29904             url: url,
29905             method: method,
29906             data: data || params || null
29907         };
29908     },
29909
29910     /**
29911      * Template method for overriding url
29912      * @private
29913      * @param {Object} options
29914      * @param {String} url
29915      * @return {String} The modified url
29916      */
29917     setupUrl: function(options, url){
29918         var form = this.getForm(options);
29919         if (form) {
29920             url = url || form.action;
29921         }
29922         return url;
29923     },
29924
29925
29926     /**
29927      * Template method for overriding params
29928      * @private
29929      * @param {Object} options
29930      * @param {String} params
29931      * @return {String} The modified params
29932      */
29933     setupParams: function(options, params) {
29934         var form = this.getForm(options),
29935             serializedForm;
29936         if (form && !this.isFormUpload(options)) {
29937             serializedForm = Ext.core.Element.serializeForm(form);
29938             params = params ? (params + '&' + serializedForm) : serializedForm;
29939         }
29940         return params;
29941     },
29942
29943     /**
29944      * Template method for overriding method
29945      * @private
29946      * @param {Object} options
29947      * @param {String} method
29948      * @return {String} The modified method
29949      */
29950     setupMethod: function(options, method){
29951         if (this.isFormUpload(options)) {
29952             return 'POST';
29953         }
29954         return method;
29955     },
29956
29957     /**
29958      * Setup all the headers for the request
29959      * @private
29960      * @param {Object} xhr The xhr object
29961      * @param {Object} options The options for the request
29962      * @param {Object} data The data for the request
29963      * @param {Object} params The params for the request
29964      */
29965     setupHeaders: function(xhr, options, data, params){
29966         var me = this,
29967             headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
29968             contentType = me.defaultPostHeader,
29969             jsonData = options.jsonData,
29970             xmlData = options.xmlData,
29971             key,
29972             header;
29973
29974         if (!headers['Content-Type'] && (data || params)) {
29975             if (data) {
29976                 if (options.rawData) {
29977                     contentType = 'text/plain';
29978                 } else {
29979                     if (xmlData && Ext.isDefined(xmlData)) {
29980                         contentType = 'text/xml';
29981                     } else if (jsonData && Ext.isDefined(jsonData)) {
29982                         contentType = 'application/json';
29983                     }
29984                 }
29985             }
29986             headers['Content-Type'] = contentType;
29987         }
29988
29989         if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
29990             headers['X-Requested-With'] = me.defaultXhrHeader;
29991         }
29992         // set up all the request headers on the xhr object
29993         try{
29994             for (key in headers) {
29995                 if (headers.hasOwnProperty(key)) {
29996                     header = headers[key];
29997                     xhr.setRequestHeader(key, header);
29998                 }
29999
30000             }
30001         } catch(e) {
30002             me.fireEvent('exception', key, header);
30003         }
30004         return headers;
30005     },
30006
30007     /**
30008      * Creates the appropriate XHR transport for the browser.
30009      * @private
30010      */
30011     getXhrInstance: (function(){
30012         var options = [function(){
30013             return new XMLHttpRequest();
30014         }, function(){
30015             return new ActiveXObject('MSXML2.XMLHTTP.3.0');
30016         }, function(){
30017             return new ActiveXObject('MSXML2.XMLHTTP');
30018         }, function(){
30019             return new ActiveXObject('Microsoft.XMLHTTP');
30020         }], i = 0,
30021             len = options.length,
30022             xhr;
30023
30024         for(; i < len; ++i) {
30025             try{
30026                 xhr = options[i];
30027                 xhr();
30028                 break;
30029             }catch(e){}
30030         }
30031         return xhr;
30032     })(),
30033
30034     /**
30035      * Determine whether this object has a request outstanding.
30036      * @param {Object} request (Optional) defaults to the last transaction
30037      * @return {Boolean} True if there is an outstanding request.
30038      */
30039     isLoading : function(request) {
30040         if (!(request && request.xhr)) {
30041             return false;
30042         }
30043         // if there is a connection and readyState is not 0 or 4
30044         var state = request.xhr.readyState;
30045         return !(state === 0 || state == 4);
30046     },
30047
30048     /**
30049      * Aborts any outstanding request.
30050      * @param {Object} request (Optional) defaults to the last request
30051      */
30052     abort : function(request) {
30053         var me = this,
30054             requests = me.requests,
30055             id;
30056
30057         if (request && me.isLoading(request)) {
30058             /*
30059              * Clear out the onreadystatechange here, this allows us
30060              * greater control, the browser may/may not fire the function
30061              * depending on a series of conditions.
30062              */
30063             request.xhr.onreadystatechange = null;
30064             request.xhr.abort();
30065             me.clearTimeout(request);
30066             if (!request.timedout) {
30067                 request.aborted = true;
30068             }
30069             me.onComplete(request);
30070             me.cleanup(request);
30071         } else if (!request) {
30072             for(id in requests) {
30073                 if (requests.hasOwnProperty(id)) {
30074                     me.abort(requests[id]);
30075                 }
30076             }
30077         }
30078     },
30079
30080     /**
30081      * Fires when the state of the xhr changes
30082      * @private
30083      * @param {Object} request The request
30084      */
30085     onStateChange : function(request) {
30086         if (request.xhr.readyState == 4) {
30087             this.clearTimeout(request);
30088             this.onComplete(request);
30089             this.cleanup(request);
30090         }
30091     },
30092
30093     /**
30094      * Clear the timeout on the request
30095      * @private
30096      * @param {Object} The request
30097      */
30098     clearTimeout: function(request){
30099         clearTimeout(request.timeout);
30100         delete request.timeout;
30101     },
30102
30103     /**
30104      * Clean up any left over information from the request
30105      * @private
30106      * @param {Object} The request
30107      */
30108     cleanup: function(request){
30109         request.xhr = null;
30110         delete request.xhr;
30111     },
30112
30113     /**
30114      * To be called when the request has come back from the server
30115      * @private
30116      * @param {Object} request
30117      * @return {Object} The response
30118      */
30119     onComplete : function(request) {
30120         var me = this,
30121             options = request.options,
30122             result = me.parseStatus(request.xhr.status),
30123             success = result.success,
30124             response;
30125
30126         if (success) {
30127             response = me.createResponse(request);
30128             me.fireEvent('requestcomplete', me, response, options);
30129             Ext.callback(options.success, options.scope, [response, options]);
30130         } else {
30131             if (result.isException || request.aborted || request.timedout) {
30132                 response = me.createException(request);
30133             } else {
30134                 response = me.createResponse(request);
30135             }
30136             me.fireEvent('requestexception', me, response, options);
30137             Ext.callback(options.failure, options.scope, [response, options]);
30138         }
30139         Ext.callback(options.callback, options.scope, [options, success, response]);
30140         delete me.requests[request.id];
30141         return response;
30142     },
30143
30144     /**
30145      * Check if the response status was successful
30146      * @param {Number} status The status code
30147      * @return {Object} An object containing success/status state
30148      */
30149     parseStatus: function(status) {
30150         // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
30151         status = status == 1223 ? 204 : status;
30152
30153         var success = (status >= 200 && status < 300) || status == 304,
30154             isException = false;
30155
30156         if (!success) {
30157             switch (status) {
30158                 case 12002:
30159                 case 12029:
30160                 case 12030:
30161                 case 12031:
30162                 case 12152:
30163                 case 13030:
30164                     isException = true;
30165                     break;
30166             }
30167         }
30168         return {
30169             success: success,
30170             isException: isException
30171         };
30172     },
30173
30174     /**
30175      * Create the response object
30176      * @private
30177      * @param {Object} request
30178      */
30179     createResponse : function(request) {
30180         var xhr = request.xhr,
30181             headers = {},
30182             lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
30183             count = lines.length,
30184             line, index, key, value, response;
30185
30186         while (count--) {
30187             line = lines[count];
30188             index = line.indexOf(':');
30189             if(index >= 0) {
30190                 key = line.substr(0, index).toLowerCase();
30191                 if (line.charAt(index + 1) == ' ') {
30192                     ++index;
30193                 }
30194                 headers[key] = line.substr(index + 1);
30195             }
30196         }
30197
30198         request.xhr = null;
30199         delete request.xhr;
30200
30201         response = {
30202             request: request,
30203             requestId : request.id,
30204             status : xhr.status,
30205             statusText : xhr.statusText,
30206             getResponseHeader : function(header){ return headers[header.toLowerCase()]; },
30207             getAllResponseHeaders : function(){ return headers; },
30208             responseText : xhr.responseText,
30209             responseXML : xhr.responseXML
30210         };
30211
30212         // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
30213         // functions created with getResponseHeader/getAllResponseHeaders
30214         xhr = null;
30215         return response;
30216     },
30217
30218     /**
30219      * Create the exception object
30220      * @private
30221      * @param {Object} request
30222      */
30223     createException : function(request) {
30224         return {
30225             request : request,
30226             requestId : request.id,
30227             status : request.aborted ? -1 : 0,
30228             statusText : request.aborted ? 'transaction aborted' : 'communication failure',
30229             aborted: request.aborted,
30230             timedout: request.timedout
30231         };
30232     }
30233 });
30234
30235 /**
30236  * @class Ext.Ajax
30237  * @singleton
30238  * @markdown
30239  * @extends Ext.data.Connection
30240
30241 A singleton instance of an {@link Ext.data.Connection}. This class
30242 is used to communicate with your server side code. It can be used as follows:
30243
30244     Ext.Ajax.request({
30245         url: 'page.php',
30246         params: {
30247             id: 1
30248         },
30249         success: function(response){
30250             var text = response.responseText;
30251             // process server response here
30252         }
30253     });
30254
30255 Default options for all requests can be set by changing a property on the Ext.Ajax class:
30256
30257     Ext.Ajax.timeout = 60000; // 60 seconds
30258
30259 Any options specified in the request method for the Ajax request will override any
30260 defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
30261 request will be 60 seconds.
30262
30263     Ext.Ajax.timeout = 120000; // 120 seconds
30264     Ext.Ajax.request({
30265         url: 'page.aspx',
30266         timeout: 60000
30267     });
30268
30269 In general, this class will be used for all Ajax requests in your application.
30270 The main reason for creating a separate {@link Ext.data.Connection} is for a
30271 series of requests that share common settings that are different to all other
30272 requests in the application.
30273
30274  */
30275 Ext.define('Ext.Ajax', {
30276     extend: 'Ext.data.Connection',
30277     singleton: true,
30278
30279     /**
30280      * @cfg {String} url @hide
30281      */
30282     /**
30283      * @cfg {Object} extraParams @hide
30284      */
30285     /**
30286      * @cfg {Object} defaultHeaders @hide
30287      */
30288     /**
30289      * @cfg {String} method (Optional) @hide
30290      */
30291     /**
30292      * @cfg {Number} timeout (Optional) @hide
30293      */
30294     /**
30295      * @cfg {Boolean} autoAbort (Optional) @hide
30296      */
30297
30298     /**
30299      * @cfg {Boolean} disableCaching (Optional) @hide
30300      */
30301
30302     /**
30303      * @property  disableCaching
30304      * True to add a unique cache-buster param to GET requests. (defaults to true)
30305      * @type Boolean
30306      */
30307     /**
30308      * @property  url
30309      * The default URL to be used for requests to the server. (defaults to undefined)
30310      * If the server receives all requests through one URL, setting this once is easier than
30311      * entering it on every request.
30312      * @type String
30313      */
30314     /**
30315      * @property  extraParams
30316      * An object containing properties which are used as extra parameters to each request made
30317      * by this object (defaults to undefined). Session information and other data that you need
30318      * to pass with each request are commonly put here.
30319      * @type Object
30320      */
30321     /**
30322      * @property  defaultHeaders
30323      * An object containing request headers which are added to each request made by this object
30324      * (defaults to undefined).
30325      * @type Object
30326      */
30327     /**
30328      * @property  method
30329      * The default HTTP method to be used for requests. Note that this is case-sensitive and
30330      * should be all caps (defaults to undefined; if not set but params are present will use
30331      * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
30332      * @type String
30333      */
30334     /**
30335      * @property  timeout
30336      * The timeout in milliseconds to be used for requests. (defaults to 30000)
30337      * @type Number
30338      */
30339
30340     /**
30341      * @property  autoAbort
30342      * Whether a new request should abort any pending requests. (defaults to false)
30343      * @type Boolean
30344      */
30345     autoAbort : false
30346 });
30347 /**
30348  * @author Ed Spencer
30349  * @class Ext.data.Association
30350  * @extends Object
30351  *
30352  * <p>Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
30353  * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
30354  * express like this:</p>
30355  *
30356 <pre><code>
30357 Ext.define('User', {
30358     extend: 'Ext.data.Model',
30359     fields: ['id', 'name', 'email'],
30360
30361     hasMany: {model: 'Order', name: 'orders'}
30362 });
30363
30364 Ext.define('Order', {
30365     extend: 'Ext.data.Model',
30366     fields: ['id', 'user_id', 'status', 'price'],
30367
30368     belongsTo: 'User'
30369 });
30370 </code></pre>
30371  *
30372  * <p>We've set up two models - User and Order - and told them about each other. You can set up as many associations on
30373  * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and
30374  * {@link Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
30375  * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.</p>
30376  *
30377  * <p><u>Further Reading</u></p>
30378  *
30379  * <ul style="list-style-type: disc; padding-left: 20px;">
30380  *   <li>{@link Ext.data.HasManyAssociation hasMany associations}
30381  *   <li>{@link Ext.data.BelongsToAssociation belongsTo associations}
30382  *   <li>{@link Ext.data.Model using Models}
30383  * </ul>
30384  * 
30385  * <b>Self association models</b>
30386  * <p>We can also have models that create parent/child associations between the same type. Below is an example, where
30387  * groups can be nested inside other groups:</p>
30388  * <pre><code>
30389
30390 // Server Data
30391 {
30392     "groups": {
30393         "id": 10,
30394         "parent_id": 100,
30395         "name": "Main Group",
30396         "parent_group": {
30397             "id": 100,
30398             "parent_id": null,
30399             "name": "Parent Group"
30400         },
30401         "child_groups": [{
30402             "id": 2,
30403             "parent_id": 10,
30404             "name": "Child Group 1"
30405         },{
30406             "id": 3,
30407             "parent_id": 10,
30408             "name": "Child Group 2"
30409         },{
30410             "id": 4,
30411             "parent_id": 10,
30412             "name": "Child Group 3"
30413         }]
30414     }
30415 }
30416
30417 // Client code
30418 Ext.define('Group', {
30419     extend: 'Ext.data.Model',
30420     fields: ['id', 'parent_id', 'name'],
30421     proxy: {
30422         type: 'ajax',
30423         url: 'data.json',
30424         reader: {
30425             type: 'json',
30426             root: 'groups'
30427         }
30428     },
30429     associations: [{
30430         type: 'hasMany',
30431         model: 'Group',
30432         primaryKey: 'id',
30433         foreignKey: 'parent_id',
30434         autoLoad: true,
30435         associationKey: 'child_groups' // read child data from child_groups
30436     }, {
30437         type: 'belongsTo',
30438         model: 'Group',
30439         primaryKey: 'id',
30440         foreignKey: 'parent_id',
30441         autoLoad: true,
30442         associationKey: 'parent_group' // read parent data from parent_group
30443     }]
30444 });
30445
30446
30447 Ext.onReady(function(){
30448     
30449     Group.load(10, {
30450         success: function(group){
30451             console.log(group.getGroup().get('name'));
30452             
30453             group.groups().each(function(rec){
30454                 console.log(rec.get('name'));
30455             });
30456         }
30457     });
30458     
30459 });
30460  * </code></pre>
30461  *
30462  * @constructor
30463  * @param {Object} config Optional config object
30464  */
30465 Ext.define('Ext.data.Association', {
30466     /**
30467      * @cfg {String} ownerModel The string name of the model that owns the association. Required
30468      */
30469
30470     /**
30471      * @cfg {String} associatedModel The string name of the model that is being associated with. Required
30472      */
30473
30474     /**
30475      * @cfg {String} primaryKey The name of the primary key on the associated model. Defaults to 'id'.
30476      * In general this will be the {@link Ext.data.Model#idProperty} of the Model.
30477      */
30478     primaryKey: 'id',
30479
30480     /**
30481      * @cfg {Ext.data.reader.Reader} reader A special reader to read associated data
30482      */
30483     
30484     /**
30485      * @cfg {String} associationKey The name of the property in the data to read the association from.
30486      * Defaults to the name of the associated model.
30487      */
30488
30489     defaultReaderType: 'json',
30490
30491     statics: {
30492         create: function(association){
30493             if (!association.isAssociation) {
30494                 if (Ext.isString(association)) {
30495                     association = {
30496                         type: association
30497                     };
30498                 }
30499
30500                 switch (association.type) {
30501                     case 'belongsTo':
30502                         return Ext.create('Ext.data.BelongsToAssociation', association);
30503                     case 'hasMany':
30504                         return Ext.create('Ext.data.HasManyAssociation', association);
30505                     //TODO Add this back when it's fixed
30506 //                    case 'polymorphic':
30507 //                        return Ext.create('Ext.data.PolymorphicAssociation', association);
30508                     default:
30509                         Ext.Error.raise('Unknown Association type: "' + association.type + '"');
30510                 }
30511             }
30512             return association;
30513         }
30514     },
30515
30516     constructor: function(config) {
30517         Ext.apply(this, config);
30518
30519         var types           = Ext.ModelManager.types,
30520             ownerName       = config.ownerModel,
30521             associatedName  = config.associatedModel,
30522             ownerModel      = types[ownerName],
30523             associatedModel = types[associatedName],
30524             ownerProto;
30525
30526         if (ownerModel === undefined) {
30527             Ext.Error.raise("The configured ownerModel was not valid (you tried " + ownerName + ")");
30528         }
30529         if (associatedModel === undefined) {
30530             Ext.Error.raise("The configured associatedModel was not valid (you tried " + associatedName + ")");
30531         }
30532
30533         this.ownerModel = ownerModel;
30534         this.associatedModel = associatedModel;
30535
30536         /**
30537          * The name of the model that 'owns' the association
30538          * @property ownerName
30539          * @type String
30540          */
30541
30542         /**
30543          * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is 'Order')
30544          * @property associatedName
30545          * @type String
30546          */
30547
30548         Ext.applyIf(this, {
30549             ownerName : ownerName,
30550             associatedName: associatedName
30551         });
30552     },
30553
30554     /**
30555      * Get a specialized reader for reading associated data
30556      * @return {Ext.data.reader.Reader} The reader, null if not supplied
30557      */
30558     getReader: function(){
30559         var me = this,
30560             reader = me.reader,
30561             model = me.associatedModel;
30562
30563         if (reader) {
30564             if (Ext.isString(reader)) {
30565                 reader = {
30566                     type: reader
30567                 };
30568             }
30569             if (reader.isReader) {
30570                 reader.setModel(model);
30571             } else {
30572                 Ext.applyIf(reader, {
30573                     model: model,
30574                     type : me.defaultReaderType
30575                 });
30576             }
30577             me.reader = Ext.createByAlias('reader.' + reader.type, reader);
30578         }
30579         return me.reader || null;
30580     }
30581 });
30582
30583 /**
30584  * @author Ed Spencer
30585  * @class Ext.ModelManager
30586  * @extends Ext.AbstractManager
30587
30588 The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
30589
30590 __Creating Model Instances__
30591 Model instances can be created by using the {@link #create} function. It is also possible to do
30592 this by using the Model type directly. The following snippets are equivalent:
30593
30594     Ext.define('User', {
30595         extend: 'Ext.data.Model',
30596         fields: ['first', 'last']
30597     });
30598     
30599     // method 1, create through the manager
30600     Ext.ModelManager.create({
30601         first: 'Ed',
30602         last: 'Spencer'
30603     }, 'User');
30604     
30605     // method 2, create on the type directly
30606     new User({
30607         first: 'Ed',
30608         last: 'Spencer'
30609     });
30610     
30611 __Accessing Model Types__
30612 A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
30613 are normal classes, you can access the type directly. The following snippets are equivalent:
30614
30615     Ext.define('User', {
30616         extend: 'Ext.data.Model',
30617         fields: ['first', 'last']
30618     });
30619     
30620     // method 1, access model type through the manager
30621     var UserType = Ext.ModelManager.getModel('User');
30622     
30623     // method 2, reference the type directly
30624     var UserType = User;
30625
30626  * @markdown
30627  * @singleton
30628  */
30629 Ext.define('Ext.ModelManager', {
30630     extend: 'Ext.AbstractManager',
30631     alternateClassName: 'Ext.ModelMgr',
30632     requires: ['Ext.data.Association'],
30633     
30634     singleton: true,
30635     
30636     typeName: 'mtype',
30637     
30638     /**
30639      * Private stack of associations that must be created once their associated model has been defined
30640      * @property associationStack
30641      * @type Array
30642      */
30643     associationStack: [],
30644     
30645     /**
30646      * Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
30647      * immediately, as are any addition plugins defined in the model config.
30648      * @private
30649      */
30650     registerType: function(name, config) {
30651         var proto = config.prototype,
30652             model;
30653         if (proto && proto.isModel) {
30654             // registering an already defined model
30655             model = config;
30656         } else {
30657             // passing in a configuration
30658             if (!config.extend) {
30659                 config.extend = 'Ext.data.Model';
30660             }
30661             model = Ext.define(name, config);
30662         }
30663         this.types[name] = model;
30664         return model;
30665     },
30666     
30667     /**
30668      * @private
30669      * Private callback called whenever a model has just been defined. This sets up any associations
30670      * that were waiting for the given model to be defined
30671      * @param {Function} model The model that was just created
30672      */
30673     onModelDefined: function(model) {
30674         var stack  = this.associationStack,
30675             length = stack.length,
30676             create = [],
30677             association, i, created;
30678         
30679         for (i = 0; i < length; i++) {
30680             association = stack[i];
30681             
30682             if (association.associatedModel == model.modelName) {
30683                 create.push(association);
30684             }
30685         }
30686         
30687         for (i = 0, length = create.length; i < length; i++) {
30688             created = create[i];
30689             this.types[created.ownerModel].prototype.associations.add(Ext.data.Association.create(created));
30690             Ext.Array.remove(stack, created);
30691         }
30692     },
30693     
30694     /**
30695      * Registers an association where one of the models defined doesn't exist yet.
30696      * The ModelManager will check when new models are registered if it can link them
30697      * together
30698      * @private
30699      * @param {Ext.data.Association} association The association
30700      */
30701     registerDeferredAssociation: function(association){
30702         this.associationStack.push(association);
30703     },
30704     
30705     /**
30706      * Returns the {@link Ext.data.Model} for a given model name
30707      * @param {String/Object} id The id of the model or the model instance.
30708      */
30709     getModel: function(id) {
30710         var model = id;
30711         if (typeof model == 'string') {
30712             model = this.types[model];
30713         }
30714         return model;
30715     },
30716     
30717     /**
30718      * Creates a new instance of a Model using the given data.
30719      * @param {Object} data Data to initialize the Model's fields with
30720      * @param {String} name The name of the model to create
30721      * @param {Number} id Optional unique id of the Model instance (see {@link Ext.data.Model})
30722      */
30723     create: function(config, name, id) {
30724         var con = typeof name == 'function' ? name : this.types[name || config.name];
30725         
30726         return new con(config, id);
30727     }
30728 }, function() {
30729     
30730     /**
30731      * Creates a new Model class from the specified config object. See {@link Ext.data.Model} for full examples.
30732      * 
30733      * @param {Object} config A configuration object for the Model you wish to create.
30734      * @return {Ext.data.Model} The newly registered Model
30735      * @member Ext
30736      * @method regModel
30737      */
30738     Ext.regModel = function() {
30739         if (Ext.isDefined(Ext.global.console)) {
30740             Ext.global.console.warn('Ext.regModel has been deprecated. Models can now be created by extending Ext.data.Model: Ext.define("MyModel", {extend: "Ext.data.Model", fields: []});.');
30741         }
30742         return this.ModelManager.registerType.apply(this.ModelManager, arguments);
30743     };
30744 });
30745
30746 /**
30747  * @class Ext.app.Controller
30748  * 
30749  * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
30750  * views) and take some action. Here's how we might create a Controller to manage Users:
30751  * 
30752  *     Ext.define('MyApp.controller.Users', {
30753  *         extend: 'Ext.app.Controller',
30754  * 
30755  *         init: function() {
30756  *             console.log('Initialized Users! This happens before the Application launch function is called');
30757  *         }
30758  *     });
30759  * 
30760  * The init function is a special method that is called when your application boots. It is called before the 
30761  * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
30762  * your Viewport is created.
30763  * 
30764  * The init function is a great place to set up how your controller interacts with the view, and is usually used in 
30765  * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function 
30766  * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
30767  * our Users controller to tell us when the panel is rendered:
30768  * 
30769  *     Ext.define('MyApp.controller.Users', {
30770  *         extend: 'Ext.app.Controller',
30771  * 
30772  *         init: function() {
30773  *             this.control({
30774  *                 'viewport > panel': {
30775  *                     render: this.onPanelRendered
30776  *                 }
30777  *             });
30778  *         },
30779  * 
30780  *         onPanelRendered: function() {
30781  *             console.log('The panel was rendered');
30782  *         }
30783  *     });
30784  * 
30785  * We've updated the init function to use this.control to set up listeners on views in our application. The control
30786  * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
30787  * are not familiar with ComponentQuery yet, be sure to check out THIS GUIDE for a full explanation. In brief though,
30788  * it allows us to pass a CSS-like selector that will find every matching component on the page.
30789  * 
30790  * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
30791  * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler 
30792  * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our 
30793  * onPanelRendered function is called.
30794  * 
30795  * <u>Using refs</u>
30796  * 
30797  * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
30798  * make it really easy to get references to Views on your page. Let's look at an example of this now:
30799  * 
30800  *     Ext.define('MyApp.controller.Users', {
30801  *         extend: 'Ext.app.Controller',
30802  *     
30803  *         refs: [
30804  *             {
30805  *                 ref: 'list',
30806  *                 selector: 'grid'
30807  *             }
30808  *         ],
30809  *     
30810  *         init: function() {
30811  *             this.control({
30812  *                 'button': {
30813  *                     click: this.refreshGrid
30814  *                 }
30815  *             });
30816  *         },
30817  *     
30818  *         refreshGrid: function() {
30819  *             this.getList().store.load();
30820  *         }
30821  *     });
30822  * 
30823  * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to 
30824  * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this - 
30825  * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
30826  * assigns it to the reference 'list'.
30827  * 
30828  * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
30829  * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which 
30830  * was capitalized and prepended with get to go from 'list' to 'getList'.
30831  * 
30832  * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
30833  * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will 
30834  * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
30835  * match a single View in your application (in the case above our selector will match any grid on the page).
30836  * 
30837  * Bringing it all together, our init function is called when the application boots, at which time we call this.control
30838  * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will 
30839  * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
30840  * simplicity). When the button is clicked we use out getList function to refresh the grid.
30841  * 
30842  * You can create any number of refs and control any number of components this way, simply adding more functions to 
30843  * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the 
30844  * examples/app/feed-viewer folder in the SDK download.
30845  * 
30846  * <u>Generated getter methods</u>
30847  * 
30848  * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and 
30849  * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
30850  * 
30851  *     Ext.define('MyApp.controller.Users', {
30852  *         extend: 'Ext.app.Controller',
30853  *     
30854  *         models: ['User'],
30855  *         stores: ['AllUsers', 'AdminUsers'],
30856  *     
30857  *         init: function() {
30858  *             var User = this.getUserModel(),
30859  *                 allUsers = this.getAllUsersStore();
30860  *     
30861  *             var ed = new User({name: 'Ed'});
30862  *             allUsers.add(ed);
30863  *         }
30864  *     });
30865  * 
30866  * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
30867  * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter 
30868  * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
30869  * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the 
30870  * functionality.
30871  * 
30872  * <u>Further Reading</u>
30873  * 
30874  * For more information about writing Ext JS 4 applications, please see the <a href="../guide/application_architecture">
30875  * application architecture guide</a>. Also see the {@link Ext.app.Application} documentation.
30876  * 
30877  * @docauthor Ed Spencer
30878  * @constructor
30879  */  
30880 Ext.define('Ext.app.Controller', {
30881     /**
30882      * @cfg {String} id The id of this controller. You can use this id when dispatching.
30883      */
30884
30885     mixins: {
30886         observable: 'Ext.util.Observable'
30887     },
30888
30889     onClassExtended: function(cls, data) {
30890         var className = Ext.getClassName(cls),
30891             match = className.match(/^(.*)\.controller\./);
30892
30893         if (match !== null) {
30894             var namespace = Ext.Loader.getPrefix(className) || match[1],
30895                 onBeforeClassCreated = data.onBeforeClassCreated,
30896                 requires = [],
30897                 modules = ['model', 'view', 'store'],
30898                 prefix;
30899
30900             data.onBeforeClassCreated = function(cls, data) {
30901                 var i, ln, module,
30902                     items, j, subLn, item;
30903
30904                 for (i = 0,ln = modules.length; i < ln; i++) {
30905                     module = modules[i];
30906
30907                     items = Ext.Array.from(data[module + 's']);
30908
30909                     for (j = 0,subLn = items.length; j < subLn; j++) {
30910                         item = items[j];
30911
30912                         prefix = Ext.Loader.getPrefix(item);
30913
30914                         if (prefix === '' || prefix === item) {
30915                             requires.push(namespace + '.' + module + '.' + item);
30916                         }
30917                         else {
30918                             requires.push(item);
30919                         }
30920                     }
30921                 }
30922
30923                 Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
30924             };
30925         }
30926     },
30927
30928     constructor: function(config) {
30929         this.mixins.observable.constructor.call(this, config);
30930
30931         Ext.apply(this, config || {});
30932
30933         this.createGetters('model', this.models);
30934         this.createGetters('store', this.stores);
30935         this.createGetters('view', this.views);
30936
30937         if (this.refs) {
30938             this.ref(this.refs);
30939         }
30940     },
30941
30942     // Template method
30943     init: function(application) {},
30944     // Template method
30945     onLaunch: function(application) {},
30946
30947     createGetters: function(type, refs) {
30948         type = Ext.String.capitalize(type);
30949         Ext.Array.each(refs, function(ref) {
30950             var fn = 'get',
30951                 parts = ref.split('.');
30952
30953             // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
30954             Ext.Array.each(parts, function(part) {
30955                 fn += Ext.String.capitalize(part);
30956             });
30957             fn += type;
30958
30959             if (!this[fn]) {
30960                 this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
30961             }
30962             // Execute it right away
30963             this[fn](ref);
30964         },
30965         this);
30966     },
30967
30968     ref: function(refs) {
30969         var me = this;
30970         refs = Ext.Array.from(refs);
30971         Ext.Array.each(refs, function(info) {
30972             var ref = info.ref,
30973                 fn = 'get' + Ext.String.capitalize(ref);
30974             if (!me[fn]) {
30975                 me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
30976             }
30977         });
30978     },
30979
30980     getRef: function(ref, info, config) {
30981         this.refCache = this.refCache || {};
30982         info = info || {};
30983         config = config || {};
30984
30985         Ext.apply(info, config);
30986
30987         if (info.forceCreate) {
30988             return Ext.ComponentManager.create(info, 'component');
30989         }
30990
30991         var me = this,
30992             selector = info.selector,
30993             cached = me.refCache[ref];
30994
30995         if (!cached) {
30996             me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
30997             if (!cached && info.autoCreate) {
30998                 me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
30999             }
31000             if (cached) {
31001                 cached.on('beforedestroy', function() {
31002                     me.refCache[ref] = null;
31003                 });
31004             }
31005         }
31006
31007         return cached;
31008     },
31009
31010     control: function(selectors, listeners) {
31011         this.application.control(selectors, listeners, this);
31012     },
31013
31014     getController: function(name) {
31015         return this.application.getController(name);
31016     },
31017
31018     getStore: function(name) {
31019         return this.application.getStore(name);
31020     },
31021
31022     getModel: function(model) {
31023         return this.application.getModel(model);
31024     },
31025
31026     getView: function(view) {
31027         return this.application.getView(view);
31028     }
31029 });
31030
31031 /**
31032  * @class Ext.data.SortTypes
31033  * This class defines a series of static methods that are used on a
31034  * {@link Ext.data.Field} for performing sorting. The methods cast the 
31035  * underlying values into a data type that is appropriate for sorting on
31036  * that particular field.  If a {@link Ext.data.Field#type} is specified, 
31037  * the sortType will be set to a sane default if the sortType is not 
31038  * explicitly defined on the field. The sortType will make any necessary
31039  * modifications to the value and return it.
31040  * <ul>
31041  * <li><b>asText</b> - Removes any tags and converts the value to a string</li>
31042  * <li><b>asUCText</b> - Removes any tags and converts the value to an uppercase string</li>
31043  * <li><b>asUCText</b> - Converts the value to an uppercase string</li>
31044  * <li><b>asDate</b> - Converts the value into Unix epoch time</li>
31045  * <li><b>asFloat</b> - Converts the value to a floating point number</li>
31046  * <li><b>asInt</b> - Converts the value to an integer number</li>
31047  * </ul>
31048  * <p>
31049  * It is also possible to create a custom sortType that can be used throughout
31050  * an application.
31051  * <pre><code>
31052 Ext.apply(Ext.data.SortTypes, {
31053     asPerson: function(person){
31054         // expects an object with a first and last name property
31055         return person.lastName.toUpperCase() + person.firstName.toLowerCase();
31056     }    
31057 });
31058
31059 Ext.define('Employee', {
31060     extend: 'Ext.data.Model',
31061     fields: [{
31062         name: 'person',
31063         sortType: 'asPerson'
31064     }, {
31065         name: 'salary',
31066         type: 'float' // sortType set to asFloat
31067     }]
31068 });
31069  * </code></pre>
31070  * </p>
31071  * @singleton
31072  * @docauthor Evan Trimboli <evan@sencha.com>
31073  */
31074 Ext.define('Ext.data.SortTypes', {
31075     
31076     singleton: true,
31077     
31078     /**
31079      * Default sort that does nothing
31080      * @param {Mixed} s The value being converted
31081      * @return {Mixed} The comparison value
31082      */
31083     none : function(s) {
31084         return s;
31085     },
31086
31087     /**
31088      * The regular expression used to strip tags
31089      * @type {RegExp}
31090      * @property
31091      */
31092     stripTagsRE : /<\/?[^>]+>/gi,
31093
31094     /**
31095      * Strips all HTML tags to sort on text only
31096      * @param {Mixed} s The value being converted
31097      * @return {String} The comparison value
31098      */
31099     asText : function(s) {
31100         return String(s).replace(this.stripTagsRE, "");
31101     },
31102
31103     /**
31104      * Strips all HTML tags to sort on text only - Case insensitive
31105      * @param {Mixed} s The value being converted
31106      * @return {String} The comparison value
31107      */
31108     asUCText : function(s) {
31109         return String(s).toUpperCase().replace(this.stripTagsRE, "");
31110     },
31111
31112     /**
31113      * Case insensitive string
31114      * @param {Mixed} s The value being converted
31115      * @return {String} The comparison value
31116      */
31117     asUCString : function(s) {
31118         return String(s).toUpperCase();
31119     },
31120
31121     /**
31122      * Date sorting
31123      * @param {Mixed} s The value being converted
31124      * @return {Number} The comparison value
31125      */
31126     asDate : function(s) {
31127         if(!s){
31128             return 0;
31129         }
31130         if(Ext.isDate(s)){
31131             return s.getTime();
31132         }
31133         return Date.parse(String(s));
31134     },
31135
31136     /**
31137      * Float sorting
31138      * @param {Mixed} s The value being converted
31139      * @return {Float} The comparison value
31140      */
31141     asFloat : function(s) {
31142         var val = parseFloat(String(s).replace(/,/g, ""));
31143         return isNaN(val) ? 0 : val;
31144     },
31145
31146     /**
31147      * Integer sorting
31148      * @param {Mixed} s The value being converted
31149      * @return {Number} The comparison value
31150      */
31151     asInt : function(s) {
31152         var val = parseInt(String(s).replace(/,/g, ""), 10);
31153         return isNaN(val) ? 0 : val;
31154     }
31155 });
31156 /**
31157  * @author Ed Spencer
31158  * @class Ext.data.Errors
31159  * @extends Ext.util.MixedCollection
31160  * 
31161  * <p>Wraps a collection of validation error responses and provides convenient functions for
31162  * accessing and errors for specific fields.</p>
31163  * 
31164  * <p>Usually this class does not need to be instantiated directly - instances are instead created
31165  * automatically when {@link Ext.data.Model#validate validate} on a model instance:</p>
31166  * 
31167 <pre><code>
31168 //validate some existing model instance - in this case it returned 2 failures messages
31169 var errors = myModel.validate();
31170
31171 errors.isValid(); //false
31172
31173 errors.length; //2
31174 errors.getByField('name');  // [{field: 'name',  message: 'must be present'}]
31175 errors.getByField('title'); // [{field: 'title', message: 'is too short'}]
31176 </code></pre>
31177  */
31178 Ext.define('Ext.data.Errors', {
31179     extend: 'Ext.util.MixedCollection',
31180     
31181     /**
31182      * Returns true if there are no errors in the collection
31183      * @return {Boolean} 
31184      */
31185     isValid: function() {
31186         return this.length === 0;
31187     },
31188     
31189     /**
31190      * Returns all of the errors for the given field
31191      * @param {String} fieldName The field to get errors for
31192      * @return {Array} All errors for the given field
31193      */
31194     getByField: function(fieldName) {
31195         var errors = [],
31196             error, field, i;
31197             
31198         for (i = 0; i < this.length; i++) {
31199             error = this.items[i];
31200             
31201             if (error.field == fieldName) {
31202                 errors.push(error);
31203             }
31204         }
31205         
31206         return errors;
31207     }
31208 });
31209
31210 /**
31211  * @author Ed Spencer
31212  * @class Ext.data.Operation
31213  * @extends Object
31214  * 
31215  * <p>Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}.
31216  * Operation objects are used to enable communication between Stores and Proxies. Application
31217  * developers should rarely need to interact with Operation objects directly.</p>
31218  * 
31219  * <p>Several Operations can be batched together in a {@link Ext.data.Batch batch}.</p>
31220  * 
31221  * @constructor
31222  * @param {Object} config Optional config object
31223  */
31224 Ext.define('Ext.data.Operation', {
31225     /**
31226      * @cfg {Boolean} synchronous True if this Operation is to be executed synchronously (defaults to true). This
31227      * property is inspected by a {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in
31228      * parallel or not.
31229      */
31230     synchronous: true,
31231     
31232     /**
31233      * @cfg {String} action The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'
31234      */
31235     action: undefined,
31236     
31237     /**
31238      * @cfg {Array} filters Optional array of filter objects. Only applies to 'read' actions.
31239      */
31240     filters: undefined,
31241     
31242     /**
31243      * @cfg {Array} sorters Optional array of sorter objects. Only applies to 'read' actions.
31244      */
31245     sorters: undefined,
31246     
31247     /**
31248      * @cfg {Object} group Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
31249      */
31250     group: undefined,
31251     
31252     /**
31253      * @cfg {Number} start The start index (offset), used in paging when running a 'read' action.
31254      */
31255     start: undefined,
31256     
31257     /**
31258      * @cfg {Number} limit The number of records to load. Used on 'read' actions when paging is being used.
31259      */
31260     limit: undefined,
31261     
31262     /**
31263      * @cfg {Ext.data.Batch} batch The batch that this Operation is a part of (optional)
31264      */
31265     batch: undefined,
31266         
31267     /**
31268      * Read-only property tracking the start status of this Operation. Use {@link #isStarted}.
31269      * @property started
31270      * @type Boolean
31271      * @private
31272      */
31273     started: false,
31274     
31275     /**
31276      * Read-only property tracking the run status of this Operation. Use {@link #isRunning}.
31277      * @property running
31278      * @type Boolean
31279      * @private
31280      */
31281     running: false,
31282     
31283     /**
31284      * Read-only property tracking the completion status of this Operation. Use {@link #isComplete}.
31285      * @property complete
31286      * @type Boolean
31287      * @private
31288      */
31289     complete: false,
31290     
31291     /**
31292      * Read-only property tracking whether the Operation was successful or not. This starts as undefined and is set to true
31293      * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
31294      * {@link #wasSuccessful} to query success status.
31295      * @property success
31296      * @type Boolean
31297      * @private
31298      */
31299     success: undefined,
31300     
31301     /**
31302      * Read-only property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
31303      * @property exception
31304      * @type Boolean
31305      * @private
31306      */
31307     exception: false,
31308     
31309     /**
31310      * The error object passed when {@link #setException} was called. This could be any object or primitive.
31311      * @property error
31312      * @type Mixed
31313      * @private
31314      */
31315     error: undefined,
31316     
31317     constructor: function(config) {
31318         Ext.apply(this, config || {});
31319     },
31320     
31321     /**
31322      * Marks the Operation as started
31323      */
31324     setStarted: function() {
31325         this.started = true;
31326         this.running = true;
31327     },
31328     
31329     /**
31330      * Marks the Operation as completed
31331      */
31332     setCompleted: function() {
31333         this.complete = true;
31334         this.running  = false;
31335     },
31336     
31337     /**
31338      * Marks the Operation as successful
31339      */
31340     setSuccessful: function() {
31341         this.success = true;
31342     },
31343     
31344     /**
31345      * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
31346      * @param {Mixed} error Optional error string/object
31347      */
31348     setException: function(error) {
31349         this.exception = true;
31350         this.success = false;
31351         this.running = false;
31352         this.error = error;
31353     },
31354     
31355     /**
31356      * Returns true if this Operation encountered an exception (see also {@link #getError})
31357      * @return {Boolean} True if there was an exception
31358      */
31359     hasException: function() {
31360         return this.exception === true;
31361     },
31362     
31363     /**
31364      * Returns the error string or object that was set using {@link #setException}
31365      * @return {Mixed} The error object
31366      */
31367     getError: function() {
31368         return this.error;
31369     },
31370     
31371     /**
31372      * Returns an array of Ext.data.Model instances as set by the Proxy.
31373      * @return {Array} Any loaded Records
31374      */
31375     getRecords: function() {
31376         var resultSet = this.getResultSet();
31377         
31378         return (resultSet === undefined ? this.records : resultSet.records);
31379     },
31380     
31381     /**
31382      * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model} instances
31383      * as well as meta data such as number of instances fetched, number available etc
31384      * @return {Ext.data.ResultSet} The ResultSet object
31385      */
31386     getResultSet: function() {
31387         return this.resultSet;
31388     },
31389     
31390     /**
31391      * Returns true if the Operation has been started. Note that the Operation may have started AND completed,
31392      * see {@link #isRunning} to test if the Operation is currently running.
31393      * @return {Boolean} True if the Operation has started
31394      */
31395     isStarted: function() {
31396         return this.started === true;
31397     },
31398     
31399     /**
31400      * Returns true if the Operation has been started but has not yet completed.
31401      * @return {Boolean} True if the Operation is currently running
31402      */
31403     isRunning: function() {
31404         return this.running === true;
31405     },
31406     
31407     /**
31408      * Returns true if the Operation has been completed
31409      * @return {Boolean} True if the Operation is complete
31410      */
31411     isComplete: function() {
31412         return this.complete === true;
31413     },
31414     
31415     /**
31416      * Returns true if the Operation has completed and was successful
31417      * @return {Boolean} True if successful
31418      */
31419     wasSuccessful: function() {
31420         return this.isComplete() && this.success === true;
31421     },
31422     
31423     /**
31424      * @private
31425      * Associates this Operation with a Batch
31426      * @param {Ext.data.Batch} batch The batch
31427      */
31428     setBatch: function(batch) {
31429         this.batch = batch;
31430     },
31431     
31432     /**
31433      * Checks whether this operation should cause writing to occur.
31434      * @return {Boolean} Whether the operation should cause a write to occur.
31435      */
31436     allowWrite: function() {
31437         return this.action != 'read';
31438     }
31439 });
31440 /**
31441  * @author Ed Spencer
31442  * @class Ext.data.validations
31443  * @extends Object
31444  * 
31445  * <p>This singleton contains a set of validation functions that can be used to validate any type
31446  * of data. They are most often used in {@link Ext.data.Model Models}, where they are automatically
31447  * set up and executed.</p>
31448  */
31449 Ext.define('Ext.data.validations', {
31450     singleton: true,
31451     
31452     /**
31453      * The default error message used when a presence validation fails
31454      * @property presenceMessage
31455      * @type String
31456      */
31457     presenceMessage: 'must be present',
31458     
31459     /**
31460      * The default error message used when a length validation fails
31461      * @property lengthMessage
31462      * @type String
31463      */
31464     lengthMessage: 'is the wrong length',
31465     
31466     /**
31467      * The default error message used when a format validation fails
31468      * @property formatMessage
31469      * @type Boolean
31470      */
31471     formatMessage: 'is the wrong format',
31472     
31473     /**
31474      * The default error message used when an inclusion validation fails
31475      * @property inclusionMessage
31476      * @type String
31477      */
31478     inclusionMessage: 'is not included in the list of acceptable values',
31479     
31480     /**
31481      * The default error message used when an exclusion validation fails
31482      * @property exclusionMessage
31483      * @type String
31484      */
31485     exclusionMessage: 'is not an acceptable value',
31486     
31487     /**
31488      * Validates that the given value is present
31489      * @param {Object} config Optional config object
31490      * @param {Mixed} value The value to validate
31491      * @return {Boolean} True if validation passed
31492      */
31493     presence: function(config, value) {
31494         if (value === undefined) {
31495             value = config;
31496         }
31497         
31498         return !!value;
31499     },
31500     
31501     /**
31502      * Returns true if the given value is between the configured min and max values
31503      * @param {Object} config Optional config object
31504      * @param {String} value The value to validate
31505      * @return {Boolean} True if the value passes validation
31506      */
31507     length: function(config, value) {
31508         if (value === undefined) {
31509             return false;
31510         }
31511         
31512         var length = value.length,
31513             min    = config.min,
31514             max    = config.max;
31515         
31516         if ((min && length < min) || (max && length > max)) {
31517             return false;
31518         } else {
31519             return true;
31520         }
31521     },
31522     
31523     /**
31524      * Returns true if the given value passes validation against the configured {@link #matcher} regex
31525      * @param {Object} config Optional config object
31526      * @param {String} value The value to validate
31527      * @return {Boolean} True if the value passes the format validation
31528      */
31529     format: function(config, value) {
31530         return !!(config.matcher && config.matcher.test(value));
31531     },
31532     
31533     /**
31534      * Validates that the given value is present in the configured {@link #list}
31535      * @param {String} value The value to validate
31536      * @return {Boolean} True if the value is present in the list
31537      */
31538     inclusion: function(config, value) {
31539         return config.list && Ext.Array.indexOf(config.list,value) != -1;
31540     },
31541     
31542     /**
31543      * Validates that the given value is present in the configured {@link #list}
31544      * @param {Object} config Optional config object
31545      * @param {String} value The value to validate
31546      * @return {Boolean} True if the value is not present in the list
31547      */
31548     exclusion: function(config, value) {
31549         return config.list && Ext.Array.indexOf(config.list,value) == -1;
31550     }
31551 });
31552 /**
31553  * @author Ed Spencer
31554  * @class Ext.data.ResultSet
31555  * @extends Object
31556  * 
31557  * <p>Simple wrapper class that represents a set of records returned by a Proxy.</p>
31558  * 
31559  * @constructor
31560  * Creates the new ResultSet
31561  */
31562 Ext.define('Ext.data.ResultSet', {
31563     /**
31564      * @cfg {Boolean} loaded
31565      * True if the records have already been loaded. This is only meaningful when dealing with
31566      * SQL-backed proxies
31567      */
31568     loaded: true,
31569     
31570     /**
31571      * @cfg {Number} count
31572      * The number of records in this ResultSet. Note that total may differ from this number
31573      */
31574     count: 0,
31575     
31576     /**
31577      * @cfg {Number} total
31578      * The total number of records reported by the data source. This ResultSet may form a subset of
31579      * those records (see count)
31580      */
31581     total: 0,
31582     
31583     /**
31584      * @cfg {Boolean} success
31585      * True if the ResultSet loaded successfully, false if any errors were encountered
31586      */
31587     success: false,
31588     
31589     /**
31590      * @cfg {Array} records The array of record instances. Required
31591      */
31592
31593     constructor: function(config) {
31594         Ext.apply(this, config);
31595         
31596         /**
31597          * DEPRECATED - will be removed in Ext JS 5.0. This is just a copy of this.total - use that instead
31598          * @property totalRecords
31599          * @type Mixed
31600          */
31601         this.totalRecords = this.total;
31602         
31603         if (config.count === undefined) {
31604             this.count = this.records.length;
31605         }
31606     }
31607 });
31608 /**
31609  * @author Ed Spencer
31610  * @class Ext.data.writer.Writer
31611  * @extends Object
31612  * 
31613  * <p>Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is
31614  * responsible for taking a set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request}
31615  * object and modifying that request based on the Operations.</p>
31616  * 
31617  * <p>For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model} 
31618  * instances based on the config options passed to the JsonWriter's constructor.</p>
31619  * 
31620  * <p>Writers are not needed for any kind of local storage - whether via a
31621  * {@link Ext.data.proxy.WebStorage Web Storage proxy} (see {@link Ext.data.proxy.LocalStorage localStorage}
31622  * and {@link Ext.data.proxy.SessionStorage sessionStorage}) or just in memory via a
31623  * {@link Ext.data.proxy.Memory MemoryProxy}.</p>
31624  * 
31625  * @constructor
31626  * @param {Object} config Optional config object
31627  */
31628 Ext.define('Ext.data.writer.Writer', {
31629     alias: 'writer.base',
31630     alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
31631     
31632     /**
31633      * @cfg {Boolean} writeAllFields True to write all fields from the record to the server. If set to false it
31634      * will only send the fields that were modified. Defaults to <tt>true</tt>. Note that any fields that have
31635      * {@link Ext.data.Field#persist} set to false will still be ignored.
31636      */
31637     writeAllFields: true,
31638     
31639     /**
31640      * @cfg {String} nameProperty This property is used to read the key for each value that will be sent to the server.
31641      * For example:
31642      * <pre><code>
31643 Ext.define('Person', {
31644     extend: 'Ext.data.Model',
31645     fields: [{
31646         name: 'first',
31647         mapping: 'firstName'
31648     }, {
31649         name: 'last',
31650         mapping: 'lastName'
31651     }, {
31652         name: 'age'
31653     }]
31654 });
31655 new Ext.data.writer.Writer({
31656     writeAllFields: true,
31657     nameProperty: 'mapping'
31658 });
31659
31660 // This will be sent to the server
31661 {
31662     firstName: 'first name value',
31663     lastName: 'last name value',
31664     age: 1
31665 }
31666
31667      * </code></pre>
31668      * Defaults to <tt>name</tt>. If the value is not present, the field name will always be used.
31669      */
31670     nameProperty: 'name',
31671
31672     constructor: function(config) {
31673         Ext.apply(this, config);
31674     },
31675
31676     /**
31677      * Prepares a Proxy's Ext.data.Request object
31678      * @param {Ext.data.Request} request The request object
31679      * @return {Ext.data.Request} The modified request object
31680      */
31681     write: function(request) {
31682         var operation = request.operation,
31683             records   = operation.records || [],
31684             len       = records.length,
31685             i         = 0,
31686             data      = [];
31687
31688         for (; i < len; i++) {
31689             data.push(this.getRecordData(records[i]));
31690         }
31691         return this.writeRecords(request, data);
31692     },
31693
31694     /**
31695      * Formats the data for each record before sending it to the server. This
31696      * method should be overridden to format the data in a way that differs from the default.
31697      * @param {Object} record The record that we are writing to the server.
31698      * @return {Object} An object literal of name/value keys to be written to the server.
31699      * By default this method returns the data property on the record.
31700      */
31701     getRecordData: function(record) {
31702         var isPhantom = record.phantom === true,
31703             writeAll = this.writeAllFields || isPhantom,
31704             nameProperty = this.nameProperty,
31705             fields = record.fields,
31706             data = {},
31707             changes,
31708             name,
31709             field,
31710             key;
31711         
31712         if (writeAll) {
31713             fields.each(function(field){
31714                 if (field.persist) {
31715                     name = field[nameProperty] || field.name;
31716                     data[name] = record.get(field.name);
31717                 }
31718             });
31719         } else {
31720             // Only write the changes
31721             changes = record.getChanges();
31722             for (key in changes) {
31723                 if (changes.hasOwnProperty(key)) {
31724                     field = fields.get(key);
31725                     name = field[nameProperty] || field.name;
31726                     data[name] = changes[key];
31727                 }
31728             }
31729             if (!isPhantom) {
31730                 // always include the id for non phantoms
31731                 data[record.idProperty] = record.getId();
31732             }
31733         }
31734         return data;
31735     }
31736 });
31737
31738 /**
31739  * @class Ext.util.Floating
31740  * A mixin to add floating capability to a Component
31741  */
31742 Ext.define('Ext.util.Floating', {
31743
31744     uses: ['Ext.Layer', 'Ext.window.Window'],
31745
31746     /**
31747      * @cfg {Boolean} focusOnToFront
31748      * Specifies whether the floated component should be automatically {@link #focus focused} when it is
31749      * {@link #toFront brought to the front}. Defaults to true.
31750      */
31751     focusOnToFront: true,
31752
31753     /**
31754      * @cfg {String/Boolean} shadow Specifies whether the floating component should be given a shadow. Set to
31755      * <tt>true</tt> to automatically create an {@link Ext.Shadow}, or a string indicating the
31756      * shadow's display {@link Ext.Shadow#mode}. Set to <tt>false</tt> to disable the shadow.
31757      * (Defaults to <tt>'sides'</tt>.)
31758      */
31759     shadow: 'sides',
31760
31761     constructor: function(config) {
31762         this.floating = true;
31763         this.el = Ext.create('Ext.Layer', Ext.apply({}, config, {
31764             hideMode: this.hideMode,
31765             hidden: this.hidden,
31766             shadow: Ext.isDefined(this.shadow) ? this.shadow : 'sides',
31767             shadowOffset: this.shadowOffset,
31768             constrain: false,
31769             shim: this.shim === false ? false : undefined
31770         }), this.el);
31771     },
31772
31773     onFloatRender: function() {
31774         var me = this;
31775         me.zIndexParent = me.getZIndexParent();
31776         me.setFloatParent(me.ownerCt);
31777         delete me.ownerCt;
31778
31779         if (me.zIndexParent) {
31780             me.zIndexParent.registerFloatingItem(me);
31781         } else {
31782             Ext.WindowManager.register(me);
31783         }
31784     },
31785
31786     setFloatParent: function(floatParent) {
31787         var me = this;
31788
31789         // Remove listeners from previous floatParent
31790         if (me.floatParent) {
31791             me.mun(me.floatParent, {
31792                 hide: me.onFloatParentHide,
31793                 show: me.onFloatParentShow,
31794                 scope: me
31795             });
31796         }
31797
31798         me.floatParent = floatParent;
31799
31800         // Floating Components as children of Containers must hide when their parent hides.
31801         if (floatParent) {
31802             me.mon(me.floatParent, {
31803                 hide: me.onFloatParentHide,
31804                 show: me.onFloatParentShow,
31805                 scope: me
31806             });
31807         }
31808
31809         // If a floating Component is configured to be constrained, but has no configured
31810         // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
31811         if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
31812             me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
31813         }
31814     },
31815
31816     onFloatParentHide: function() {
31817         this.showOnParentShow = this.isVisible();
31818         this.hide();
31819     },
31820
31821     onFloatParentShow: function() {
31822         if (this.showOnParentShow) {
31823             delete this.showOnParentShow;
31824             this.show();
31825         }
31826     },
31827
31828     /**
31829      * @private
31830      * <p>Finds the ancestor Container responsible for allocating zIndexes for the passed Component.</p>
31831      * <p>That will be the outermost floating Container (a Container which has no ownerCt and has floating:true).</p>
31832      * <p>If we have no ancestors, or we walk all the way up to the document body, there's no zIndexParent,
31833      * and the global Ext.WindowManager will be used.</p>
31834      */
31835     getZIndexParent: function() {
31836         var p = this.ownerCt,
31837             c;
31838
31839         if (p) {
31840             while (p) {
31841                 c = p;
31842                 p = p.ownerCt;
31843             }
31844             if (c.floating) {
31845                 return c;
31846             }
31847         }
31848     },
31849
31850     // private
31851     // z-index is managed by the zIndexManager and may be overwritten at any time.
31852     // Returns the next z-index to be used.
31853     // If this is a Container, then it will have rebased any managed floating Components,
31854     // and so the next available z-index will be approximately 10000 above that.
31855     setZIndex: function(index) {
31856         var me = this;
31857         this.el.setZIndex(index);
31858
31859         // Next item goes 10 above;
31860         index += 10;
31861
31862         // When a Container with floating items has its z-index set, it rebases any floating items it is managing.
31863         // The returned value is a round number approximately 10000 above the last z-index used.
31864         if (me.floatingItems) {
31865             index = Math.floor(me.floatingItems.setBase(index) / 100) * 100 + 10000;
31866         }
31867         return index;
31868     },
31869
31870     /**
31871      * <p>Moves this floating Component into a constrain region.</p>
31872      * <p>By default, this Component is constrained to be within the container it was added to, or the element
31873      * it was rendered to.</p>
31874      * <p>An alternative constraint may be passed.</p>
31875      * @param {Mixed} constrainTo Optional. The Element or {@link Ext.util.Region Region} into which this Component is to be constrained.
31876      */
31877     doConstrain: function(constrainTo) {
31878         var me = this,
31879             vector = me.getConstrainVector(constrainTo),
31880             xy;
31881
31882         if (vector) {
31883             xy = me.getPosition();
31884             xy[0] += vector[0];
31885             xy[1] += vector[1];
31886             me.setPosition(xy);
31887         }
31888     },
31889     
31890     
31891     /**
31892      * Gets the x/y offsets to constrain this float
31893      * @private
31894      * @param {Mixed} constrainTo Optional. The Element or {@link Ext.util.Region Region} into which this Component is to be constrained.
31895      * @return {Array} The x/y constraints
31896      */
31897     getConstrainVector: function(constrainTo){
31898         var me = this,
31899             el;
31900             
31901         if (me.constrain || me.constrainHeader) {
31902             el = me.constrainHeader ? me.header.el : me.el;
31903             constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container;
31904             return el.getConstrainVector(constrainTo);
31905         }
31906     },
31907
31908     /**
31909      * Aligns this floating Component to the specified element
31910      * @param {Mixed} element The element or {@link Ext.Component} to align to. If passing a component, it must
31911      * be a omponent instance. If a string id is passed, it will be used as an element id.
31912      * @param {String} position (optional, defaults to "tl-bl?") The position to align to (see {@link Ext.core.Element#alignTo} for more details).
31913      * @param {Array} offsets (optional) Offset the positioning by [x, y]
31914      * @return {Component} this
31915      */
31916     alignTo: function(element, position, offsets) {
31917         if (element.isComponent) {
31918             element = element.getEl();
31919         }
31920         var xy = this.el.getAlignToXY(element, position, offsets);
31921         this.setPagePosition(xy);
31922         return this;
31923     },
31924
31925     /**
31926      * <p>Brings this floating Component to the front of any other visible, floating Components managed by the same {@link Ext.ZIndexManager ZIndexManager}</p>
31927      * <p>If this Component is modal, inserts the modal mask just below this Component in the z-index stack.</p>
31928      * @param {Boolean} preventFocus (optional) Specify <code>true</code> to prevent the Component from being focused.
31929      * @return {Component} this
31930      */
31931     toFront: function(preventFocus) {
31932         var me = this;
31933
31934         // Find the floating Component which provides the base for this Component's zIndexing.
31935         // That must move to front to then be able to rebase its zIndex stack and move this to the front
31936         if (me.zIndexParent) {
31937             me.zIndexParent.toFront(true);
31938         }
31939         if (me.zIndexManager.bringToFront(me)) {
31940             if (!Ext.isDefined(preventFocus)) {
31941                 preventFocus = !me.focusOnToFront;
31942             }
31943             if (!preventFocus) {
31944                 // Kick off a delayed focus request.
31945                 // If another floating Component is toFronted before the delay expires
31946                 // this will not receive focus.
31947                 me.focus(false, true);
31948             }
31949         }
31950         return me;
31951     },
31952
31953     /**
31954      * <p>This method is called internally by {@link Ext.ZIndexManager} to signal that a floating
31955      * Component has either been moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.</p>
31956      * <p>If a <i>Window</i> is superceded by another Window, deactivating it hides its shadow.</p>
31957      * <p>This method also fires the {@link #activate} or {@link #deactivate} event depending on which action occurred.</p>
31958      * @param {Boolean} active True to activate the Component, false to deactivate it (defaults to false)
31959      * @param {Component} newActive The newly active Component which is taking over topmost zIndex position.
31960      */
31961     setActive: function(active, newActive) {
31962         if (active) {
31963             if ((this instanceof Ext.window.Window) && !this.maximized) {
31964                 this.el.enableShadow(true);
31965             }
31966             this.fireEvent('activate', this);
31967         } else {
31968             // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
31969             // can keep their shadows all the time
31970             if ((this instanceof Ext.window.Window) && (newActive instanceof Ext.window.Window)) {
31971                 this.el.disableShadow();
31972             }
31973             this.fireEvent('deactivate', this);
31974         }
31975     },
31976
31977     /**
31978      * Sends this Component to the back of (lower z-index than) any other visible windows
31979      * @return {Component} this
31980      */
31981     toBack: function() {
31982         this.zIndexManager.sendToBack(this);
31983         return this;
31984     },
31985
31986     /**
31987      * Center this Component in its container.
31988      * @return {Component} this
31989      */
31990     center: function() {
31991         var xy = this.el.getAlignToXY(this.container, 'c-c');
31992         this.setPagePosition(xy);
31993         return this;
31994     },
31995
31996     // private
31997     syncShadow : function(){
31998         if (this.floating) {
31999             this.el.sync(true);
32000         }
32001     },
32002
32003     // private
32004     fitContainer: function() {
32005         var parent = this.floatParent,
32006             container = parent ? parent.getTargetEl() : this.container,
32007             size = container.getViewSize(false);
32008
32009         this.setSize(size);
32010     }
32011 });
32012 /**
32013  * @class Ext.layout.container.AbstractContainer
32014  * @extends Ext.layout.Layout
32015  * Please refer to sub classes documentation
32016  */
32017
32018 Ext.define('Ext.layout.container.AbstractContainer', {
32019
32020     /* Begin Definitions */
32021
32022     extend: 'Ext.layout.Layout',
32023
32024     /* End Definitions */
32025
32026     type: 'container',
32027
32028     fixedLayout: true,
32029
32030     // @private
32031     managedHeight: true,
32032     // @private
32033     managedWidth: true,
32034
32035     /**
32036      * @cfg {Boolean} bindToOwnerCtComponent
32037      * Flag to notify the ownerCt Component on afterLayout of a change
32038      */
32039     bindToOwnerCtComponent: false,
32040
32041     /**
32042      * @cfg {Boolean} bindToOwnerCtContainer
32043      * Flag to notify the ownerCt Container on afterLayout of a change
32044      */
32045     bindToOwnerCtContainer: false,
32046
32047     /**
32048      * @cfg {String} itemCls
32049      * <p>An optional extra CSS class that will be added to the container. This can be useful for adding
32050      * customized styles to the container or any of its children using standard CSS rules. See
32051      * {@link Ext.Component}.{@link Ext.Component#ctCls ctCls} also.</p>
32052      * </p>
32053      */
32054
32055     isManaged: function(dimension) {
32056         dimension = Ext.String.capitalize(dimension);
32057         var me = this,
32058             child = me,
32059             managed = me['managed' + dimension],
32060             ancestor = me.owner.ownerCt;
32061
32062         if (ancestor && ancestor.layout) {
32063             while (ancestor && ancestor.layout) {
32064                 if (managed === false || ancestor.layout['managed' + dimension] === false) {
32065                     managed = false;
32066                     break;
32067                 }
32068                 ancestor = ancestor.ownerCt;
32069             }
32070         }
32071         return managed;
32072     },
32073
32074     layout: function() {
32075         var me = this,
32076             owner = me.owner;
32077         if (Ext.isNumber(owner.height) || owner.isViewport) {
32078             me.managedHeight = false;
32079         }
32080         if (Ext.isNumber(owner.width) || owner.isViewport) {
32081             me.managedWidth = false;
32082         }
32083         me.callParent(arguments);
32084     },
32085
32086     /**
32087     * Set the size of an item within the Container.  We should always use setCalculatedSize.
32088     * @private
32089     */
32090     setItemSize: function(item, width, height) {
32091         if (Ext.isObject(width)) {
32092             height = width.height;
32093             width = width.width;
32094         }
32095         item.setCalculatedSize(width, height, this.owner);
32096     },
32097
32098     /**
32099      * <p>Returns an array of child components either for a render phase (Performed in the beforeLayout method of the layout's
32100      * base class), or the layout phase (onLayout).</p>
32101      * @return {Array} of child components
32102      */
32103     getLayoutItems: function() {
32104         return this.owner && this.owner.items && this.owner.items.items || [];
32105     },
32106
32107     afterLayout: function() {
32108         this.owner.afterLayout(this);
32109     },
32110     /**
32111      * Returns the owner component's resize element.
32112      * @return {Ext.core.Element}
32113      */
32114      getTarget: function() {
32115          return this.owner.getTargetEl();
32116      },
32117     /**
32118      * <p>Returns the element into which rendering must take place. Defaults to the owner Container's {@link Ext.AbstractComponent#targetEl}.</p>
32119      * May be overridden in layout managers which implement an inner element.
32120      * @return {Ext.core.Element}
32121      */
32122      getRenderTarget: function() {
32123          return this.owner.getTargetEl();
32124      }
32125 });
32126
32127 /**
32128  * @class Ext.ZIndexManager
32129  * <p>A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
32130  * and Component activation behavior, including masking below the active (topmost) Component.</p>
32131  * <p>{@link Ext.Component#floating Floating} Components which are rendered directly into the document (Such as {@link Ext.window.Window Window}s which are
32132  * {@link Ext.Component#show show}n are managed by a {@link Ext.WindowManager global instance}.</p>
32133  * <p>{@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating} <i>Containers</i>
32134  * (For example a {Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window}, or a {@link Ext.menu.Menu Menu}),
32135  * are managed by a ZIndexManager owned by that floating Container. So ComboBox dropdowns within Windows will have managed z-indices
32136  * guaranteed to be correct, relative to the Window.</p>
32137  * @constructor
32138  */
32139 Ext.define('Ext.ZIndexManager', {
32140
32141     alternateClassName: 'Ext.WindowGroup',
32142
32143     statics: {
32144         zBase : 9000
32145     },
32146
32147     constructor: function(container) {
32148         var me = this;
32149
32150         me.list = {};
32151         me.zIndexStack = [];
32152         me.front = null;
32153
32154         if (container) {
32155
32156             // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
32157             if (container.isContainer) {
32158                 container.on('resize', me._onContainerResize, me);
32159                 me.zseed = Ext.Number.from(container.getEl().getStyle('zIndex'), me.getNextZSeed());
32160                 // The containing element we will be dealing with (eg masking) is the content target
32161                 me.targetEl = container.getTargetEl();
32162                 me.container = container;
32163             }
32164             // This is the ZIndexManager for a DOM element
32165             else {
32166                 Ext.EventManager.onWindowResize(me._onContainerResize, me);
32167                 me.zseed = me.getNextZSeed();
32168                 me.targetEl = Ext.get(container);
32169             }
32170         }
32171         // No container passed means we are the global WindowManager. Our target is the doc body.
32172         // DOM must be ready to collect that ref.
32173         else {
32174             Ext.EventManager.onWindowResize(me._onContainerResize, me);
32175             me.zseed = me.getNextZSeed();
32176             Ext.onDocumentReady(function() {
32177                 me.targetEl = Ext.getBody();
32178             });
32179         }
32180     },
32181
32182     getNextZSeed: function() {
32183         return (Ext.ZIndexManager.zBase += 10000);
32184     },
32185
32186     setBase: function(baseZIndex) {
32187         this.zseed = baseZIndex;
32188         return this.assignZIndices();
32189     },
32190
32191     // private
32192     assignZIndices: function() {
32193         var a = this.zIndexStack,
32194             len = a.length,
32195             i = 0,
32196             zIndex = this.zseed,
32197             comp;
32198
32199         for (; i < len; i++) {
32200             comp = a[i];
32201             if (comp && !comp.hidden) {
32202
32203                 // Setting the zIndex of a Component returns the topmost zIndex consumed by
32204                 // that Component.
32205                 // If it's just a plain floating Component such as a BoundList, then the
32206                 // return value is the passed value plus 10, ready for the next item.
32207                 // If a floating *Container* has its zIndex set, it re-orders its managed
32208                 // floating children, starting from that new base, and returns a value 10000 above
32209                 // the highest zIndex which it allocates.
32210                 zIndex = comp.setZIndex(zIndex);
32211             }
32212         }
32213         this._activateLast();
32214         return zIndex;
32215     },
32216
32217     // private
32218     _setActiveChild: function(comp) {
32219         if (comp != this.front) {
32220
32221             if (this.front) {
32222                 this.front.setActive(false, comp);
32223             }
32224             this.front = comp;
32225             if (comp) {
32226                 comp.setActive(true);
32227                 if (comp.modal) {
32228                     this._showModalMask(comp.el.getStyle('zIndex') - 4);
32229                 }
32230             }
32231         }
32232     },
32233
32234     // private
32235     _activateLast: function(justHidden) {
32236         var comp,
32237             lastActivated = false,
32238             i;
32239
32240         // Go down through the z-index stack.
32241         // Activate the next visible one down.
32242         // Keep going down to find the next visible modal one to shift the modal mask down under
32243         for (i = this.zIndexStack.length-1; i >= 0; --i) {
32244             comp = this.zIndexStack[i];
32245             if (!comp.hidden) {
32246                 if (!lastActivated) {
32247                     this._setActiveChild(comp);
32248                     lastActivated = true;
32249                 }
32250
32251                 // Move any modal mask down to just under the next modal floater down the stack
32252                 if (comp.modal) {
32253                     this._showModalMask(comp.el.getStyle('zIndex') - 4);
32254                     return;
32255                 }
32256             }
32257         }
32258
32259         // none to activate, so there must be no modal mask.
32260         // And clear the currently active property
32261         this._hideModalMask();
32262         if (!lastActivated) {
32263             this._setActiveChild(null);
32264         }
32265     },
32266
32267     _showModalMask: function(zIndex) {
32268         if (!this.mask) {
32269             this.mask = this.targetEl.createChild({
32270                 cls: Ext.baseCSSPrefix + 'mask'
32271             });
32272             this.mask.setVisibilityMode(Ext.core.Element.DISPLAY);
32273             this.mask.on('click', this._onMaskClick, this);
32274         }
32275         Ext.getBody().addCls(Ext.baseCSSPrefix + 'body-masked');
32276         this.mask.setSize(this.targetEl.getViewSize(true));
32277         this.mask.setStyle('zIndex', zIndex);
32278         this.mask.show();
32279     },
32280
32281     _hideModalMask: function() {
32282         if (this.mask) {
32283             Ext.getBody().removeCls(Ext.baseCSSPrefix + 'body-masked');
32284             this.mask.hide();
32285         }
32286     },
32287
32288     _onMaskClick: function() {
32289         if (this.front) {
32290             this.front.focus();
32291         }
32292     },
32293
32294     _onContainerResize: function() {
32295         if (this.mask && this.mask.isVisible()) {
32296             this.mask.setSize(this.targetEl.getViewSize(true));
32297         }
32298     },
32299
32300     /**
32301      * <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
32302      * need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
32303      * with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
32304      * <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
32305      * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
32306      * ZIndexManager in the desktop sample app:</p><code><pre>
32307 MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
32308 </pre></code>
32309      * @param {Component} comp The Component to register.
32310      */
32311     register : function(comp) {
32312         if (comp.zIndexManager) {
32313             comp.zIndexManager.unregister(comp);
32314         }
32315         comp.zIndexManager = this;
32316
32317         this.list[comp.id] = comp;
32318         this.zIndexStack.push(comp);
32319         comp.on('hide', this._activateLast, this);
32320     },
32321
32322     /**
32323      * <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
32324      * need to be called. Components are automatically unregistered upon destruction.
32325      * See {@link #register}.</p>
32326      * @param {Component} comp The Component to unregister.
32327      */
32328     unregister : function(comp) {
32329         delete comp.zIndexManager;
32330         if (this.list && this.list[comp.id]) {
32331             delete this.list[comp.id];
32332             comp.un('hide', this._activateLast);
32333             Ext.Array.remove(this.zIndexStack, comp);
32334
32335             // Destruction requires that the topmost visible floater be activated. Same as hiding.
32336             this._activateLast(comp);
32337         }
32338     },
32339
32340     /**
32341      * Gets a registered Component by id.
32342      * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
32343      * @return {Ext.Component}
32344      */
32345     get : function(id) {
32346         return typeof id == "object" ? id : this.list[id];
32347     },
32348
32349    /**
32350      * Brings the specified Component to the front of any other active Components in this ZIndexManager.
32351      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
32352      * @return {Boolean} True if the dialog was brought to the front, else false
32353      * if it was already in front
32354      */
32355     bringToFront : function(comp) {
32356         comp = this.get(comp);
32357         if (comp != this.front) {
32358             Ext.Array.remove(this.zIndexStack, comp);
32359             this.zIndexStack.push(comp);
32360             this.assignZIndices();
32361             return true;
32362         }
32363         if (comp.modal) {
32364             Ext.getBody().addCls(Ext.baseCSSPrefix + 'body-masked');
32365             this.mask.setSize(Ext.core.Element.getViewWidth(true), Ext.core.Element.getViewHeight(true));
32366             this.mask.show();
32367         }
32368         return false;
32369     },
32370
32371     /**
32372      * Sends the specified Component to the back of other active Components in this ZIndexManager.
32373      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
32374      * @return {Ext.Component} The Component
32375      */
32376     sendToBack : function(comp) {
32377         comp = this.get(comp);
32378         Ext.Array.remove(this.zIndexStack, comp);
32379         this.zIndexStack.unshift(comp);
32380         this.assignZIndices();
32381         return comp;
32382     },
32383
32384     /**
32385      * Hides all Components managed by this ZIndexManager.
32386      */
32387     hideAll : function() {
32388         for (var id in this.list) {
32389             if (this.list[id].isComponent && this.list[id].isVisible()) {
32390                 this.list[id].hide();
32391             }
32392         }
32393     },
32394
32395     /**
32396      * @private
32397      * Temporarily hides all currently visible managed Components. This is for when
32398      * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
32399      * they should all be hidden just for the duration of the drag.
32400      */
32401     hide: function() {
32402         var i = 0,
32403             ln = this.zIndexStack.length,
32404             comp;
32405
32406         this.tempHidden = [];
32407         for (; i < ln; i++) {
32408             comp = this.zIndexStack[i];
32409             if (comp.isVisible()) {
32410                 this.tempHidden.push(comp);
32411                 comp.hide();
32412             }
32413         }
32414     },
32415
32416     /**
32417      * @private
32418      * Restores temporarily hidden managed Components to visibility.
32419      */
32420     show: function() {
32421         var i = 0,
32422             ln = this.tempHidden.length,
32423             comp,
32424             x,
32425             y;
32426
32427         for (; i < ln; i++) {
32428             comp = this.tempHidden[i];
32429             x = comp.x;
32430             y = comp.y;
32431             comp.show();
32432             comp.setPosition(x, y);
32433         }
32434         delete this.tempHidden;
32435     },
32436
32437     /**
32438      * Gets the currently-active Component in this ZIndexManager.
32439      * @return {Ext.Component} The active Component
32440      */
32441     getActive : function() {
32442         return this.front;
32443     },
32444
32445     /**
32446      * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
32447      * The function should accept a single {@link Ext.Component} reference as its only argument and should
32448      * return true if the Component matches the search criteria, otherwise it should return false.
32449      * @param {Function} fn The search function
32450      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
32451      * that gets passed to the function if not specified)
32452      * @return {Array} An array of zero or more matching windows
32453      */
32454     getBy : function(fn, scope) {
32455         var r = [],
32456             i = 0,
32457             len = this.zIndexStack.length,
32458             comp;
32459
32460         for (; i < len; i++) {
32461             comp = this.zIndexStack[i];
32462             if (fn.call(scope||comp, comp) !== false) {
32463                 r.push(comp);
32464             }
32465         }
32466         return r;
32467     },
32468
32469     /**
32470      * Executes the specified function once for every Component in this ZIndexManager, passing each
32471      * Component as the only parameter. Returning false from the function will stop the iteration.
32472      * @param {Function} fn The function to execute for each item
32473      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
32474      */
32475     each : function(fn, scope) {
32476         var comp;
32477         for (var id in this.list) {
32478             comp = this.list[id];
32479             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32480                 return;
32481             }
32482         }
32483     },
32484
32485     /**
32486      * Executes the specified function once for every Component in this ZIndexManager, passing each
32487      * Component as the only parameter. Returning false from the function will stop the iteration.
32488      * The components are passed to the function starting at the bottom and proceeding to the top.
32489      * @param {Function} fn The function to execute for each item
32490      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
32491      * is executed. Defaults to the current Component in the iteration.
32492      */
32493     eachBottomUp: function (fn, scope) {
32494         var comp,
32495             stack = this.zIndexStack,
32496             i, n;
32497
32498         for (i = 0, n = stack.length ; i < n; i++) {
32499             comp = stack[i];
32500             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32501                 return;
32502             }
32503         }
32504     },
32505
32506     /**
32507      * Executes the specified function once for every Component in this ZIndexManager, passing each
32508      * Component as the only parameter. Returning false from the function will stop the iteration.
32509      * The components are passed to the function starting at the top and proceeding to the bottom.
32510      * @param {Function} fn The function to execute for each item
32511      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
32512      * is executed. Defaults to the current Component in the iteration.
32513      */
32514     eachTopDown: function (fn, scope) {
32515         var comp,
32516             stack = this.zIndexStack,
32517             i;
32518
32519         for (i = stack.length ; i-- > 0; ) {
32520             comp = stack[i];
32521             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32522                 return;
32523             }
32524         }
32525     },
32526
32527     destroy: function() {
32528         delete this.zIndexStack;
32529         delete this.list;
32530         delete this.container;
32531         delete this.targetEl;
32532     }
32533 }, function() {
32534     /**
32535      * @class Ext.WindowManager
32536      * @extends Ext.ZIndexManager
32537      * <p>The default global floating Component group that is available automatically.</p>
32538      * <p>This manages instances of floating Components which were rendered programatically without
32539      * being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
32540      * <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
32541      * there are managed by that ZIndexManager.</p>
32542      * @singleton
32543      */
32544     Ext.WindowManager = Ext.WindowMgr = new this();
32545 });
32546
32547 /**
32548  * @class Ext.layout.container.boxOverflow.None
32549  * @extends Object
32550  * @private
32551  * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
32552  * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
32553  * for its container.
32554  */
32555 Ext.define('Ext.layout.container.boxOverflow.None', {
32556     
32557     alternateClassName: 'Ext.layout.boxOverflow.None',
32558     
32559     constructor: function(layout, config) {
32560         this.layout = layout;
32561         Ext.apply(this, config || {});
32562     },
32563
32564     handleOverflow: Ext.emptyFn,
32565
32566     clearOverflow: Ext.emptyFn,
32567     
32568     onRemove: Ext.emptyFn,
32569
32570     /**
32571      * @private
32572      * Normalizes an item reference, string id or numerical index into a reference to the item
32573      * @param {Ext.Component|String|Number} item The item reference, id or index
32574      * @return {Ext.Component} The item
32575      */
32576     getItem: function(item) {
32577         return this.layout.owner.getComponent(item);
32578     },
32579     
32580     onRemove: Ext.emptyFn
32581 });
32582 /**
32583  * @class Ext.util.KeyMap
32584  * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
32585  * The constructor accepts the same config object as defined by {@link #addBinding}.
32586  * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
32587  * combination it will call the function with this signature (if the match is a multi-key
32588  * combination the callback will still be called only once): (String key, Ext.EventObject e)
32589  * A KeyMap can also handle a string representation of keys.<br />
32590  * Usage:
32591  <pre><code>
32592 // map one key by key code
32593 var map = new Ext.util.KeyMap("my-element", {
32594     key: 13, // or Ext.EventObject.ENTER
32595     fn: myHandler,
32596     scope: myObject
32597 });
32598
32599 // map multiple keys to one action by string
32600 var map = new Ext.util.KeyMap("my-element", {
32601     key: "a\r\n\t",
32602     fn: myHandler,
32603     scope: myObject
32604 });
32605
32606 // map multiple keys to multiple actions by strings and array of codes
32607 var map = new Ext.util.KeyMap("my-element", [
32608     {
32609         key: [10,13],
32610         fn: function(){ alert("Return was pressed"); }
32611     }, {
32612         key: "abc",
32613         fn: function(){ alert('a, b or c was pressed'); }
32614     }, {
32615         key: "\t",
32616         ctrl:true,
32617         shift:true,
32618         fn: function(){ alert('Control + shift + tab was pressed.'); }
32619     }
32620 ]);
32621 </code></pre>
32622  * <b>Note: A KeyMap starts enabled</b>
32623  * @constructor
32624  * @param {Mixed} el The element to bind to
32625  * @param {Object} binding The binding (see {@link #addBinding})
32626  * @param {String} eventName (optional) The event to bind to (defaults to "keydown")
32627  */
32628 Ext.define('Ext.util.KeyMap', {
32629     alternateClassName: 'Ext.KeyMap',
32630     
32631     constructor: function(el, binding, eventName){
32632         var me = this;
32633         
32634         Ext.apply(me, {
32635             el: Ext.get(el),
32636             eventName: eventName || me.eventName,
32637             bindings: []
32638         });
32639         if (binding) {
32640             me.addBinding(binding);
32641         }
32642         me.enable();
32643     },
32644     
32645     eventName: 'keydown',
32646
32647     /**
32648      * Add a new binding to this KeyMap. The following config object properties are supported:
32649      * <pre>
32650 Property            Type             Description
32651 ----------          ---------------  ----------------------------------------------------------------------
32652 key                 String/Array     A single keycode or an array of keycodes to handle
32653 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)
32654 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)
32655 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)
32656 handler             Function         The function to call when KeyMap finds the expected key combination
32657 fn                  Function         Alias of handler (for backwards-compatibility)
32658 scope               Object           The scope of the callback function
32659 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. 
32660 </pre>
32661      *
32662      * Usage:
32663      * <pre><code>
32664 // Create a KeyMap
32665 var map = new Ext.util.KeyMap(document, {
32666     key: Ext.EventObject.ENTER,
32667     fn: handleKey,
32668     scope: this
32669 });
32670
32671 //Add a new binding to the existing KeyMap later
32672 map.addBinding({
32673     key: 'abc',
32674     shift: true,
32675     fn: handleKey,
32676     scope: this
32677 });
32678 </code></pre>
32679      * @param {Object/Array} binding A single KeyMap config or an array of configs
32680      */
32681     addBinding : function(binding){
32682         if (Ext.isArray(binding)) {
32683             Ext.each(binding, this.addBinding, this);
32684             return;
32685         }
32686         
32687         var keyCode = binding.key,
32688             processed = false,
32689             key,
32690             keys,
32691             keyString,
32692             i,
32693             len;
32694
32695         if (Ext.isString(keyCode)) {
32696             keys = [];
32697             keyString = keyCode.toLowerCase();
32698             
32699             for (i = 0, len = keyString.length; i < len; ++i){
32700                 keys.push(keyString.charCodeAt(i));
32701             }
32702             keyCode = keys;
32703             processed = true;
32704         }
32705         
32706         if (!Ext.isArray(keyCode)) {
32707             keyCode = [keyCode];
32708         }
32709         
32710         if (!processed) {
32711             for (i = 0, len = keyCode.length; i < len; ++i) {
32712                 key = keyCode[i];
32713                 if (Ext.isString(key)) {
32714                     keyCode[i] = key.toLowerCase().charCodeAt(0);
32715                 }
32716             }
32717         }
32718         
32719         this.bindings.push(Ext.apply({
32720             keyCode: keyCode
32721         }, binding));
32722     },
32723     
32724     /**
32725      * Process any keydown events on the element
32726      * @private
32727      * @param {Ext.EventObject} event
32728      */
32729     handleKeyDown: function(event) {
32730         if (this.enabled) { //just in case
32731             var bindings = this.bindings,
32732                 i = 0,
32733                 len = bindings.length;
32734                 
32735             event = this.processEvent(event);
32736             for(; i < len; ++i){
32737                 this.processBinding(bindings[i], event);
32738             }
32739         }
32740     },
32741     
32742     /**
32743      * Ugly hack to allow this class to be tested. Currently WebKit gives
32744      * no way to raise a key event properly with both
32745      * a) A keycode
32746      * b) The alt/ctrl/shift modifiers
32747      * So we have to simulate them here. Yuk! 
32748      * This is a stub method intended to be overridden by tests.
32749      * More info: https://bugs.webkit.org/show_bug.cgi?id=16735
32750      * @private
32751      */
32752     processEvent: function(event){
32753         return event;
32754     },
32755     
32756     /**
32757      * Process a particular binding and fire the handler if necessary.
32758      * @private
32759      * @param {Object} binding The binding information
32760      * @param {Ext.EventObject} event
32761      */
32762     processBinding: function(binding, event){
32763         if (this.checkModifiers(binding, event)) {
32764             var key = event.getKey(),
32765                 handler = binding.fn || binding.handler,
32766                 scope = binding.scope || this,
32767                 keyCode = binding.keyCode,
32768                 defaultEventAction = binding.defaultEventAction,
32769                 i,
32770                 len,
32771                 keydownEvent = new Ext.EventObjectImpl(event);
32772                 
32773             
32774             for (i = 0, len = keyCode.length; i < len; ++i) {
32775                 if (key === keyCode[i]) {
32776                     if (handler.call(scope, key, event) !== true && defaultEventAction) {
32777                         keydownEvent[defaultEventAction]();
32778                     }
32779                     break;
32780                 }
32781             }
32782         }
32783     },
32784     
32785     /**
32786      * Check if the modifiers on the event match those on the binding
32787      * @private
32788      * @param {Object} binding
32789      * @param {Ext.EventObject} event
32790      * @return {Boolean} True if the event matches the binding
32791      */
32792     checkModifiers: function(binding, e){
32793         var keys = ['shift', 'ctrl', 'alt'],
32794             i = 0,
32795             len = keys.length,
32796             val, key;
32797             
32798         for (; i < len; ++i){
32799             key = keys[i];
32800             val = binding[key];
32801             if (!(val === undefined || (val === e[key + 'Key']))) {
32802                 return false;
32803             }
32804         }
32805         return true;
32806     },
32807
32808     /**
32809      * Shorthand for adding a single key listener
32810      * @param {Number/Array/Object} key Either the numeric key code, array of key codes or an object with the
32811      * following options:
32812      * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
32813      * @param {Function} fn The function to call
32814      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
32815      */
32816     on: function(key, fn, scope) {
32817         var keyCode, shift, ctrl, alt;
32818         if (Ext.isObject(key) && !Ext.isArray(key)) {
32819             keyCode = key.key;
32820             shift = key.shift;
32821             ctrl = key.ctrl;
32822             alt = key.alt;
32823         } else {
32824             keyCode = key;
32825         }
32826         this.addBinding({
32827             key: keyCode,
32828             shift: shift,
32829             ctrl: ctrl,
32830             alt: alt,
32831             fn: fn,
32832             scope: scope
32833         });
32834     },
32835
32836     /**
32837      * Returns true if this KeyMap is enabled
32838      * @return {Boolean}
32839      */
32840     isEnabled : function(){
32841         return this.enabled;
32842     },
32843
32844     /**
32845      * Enables this KeyMap
32846      */
32847     enable: function(){
32848         if(!this.enabled){
32849             this.el.on(this.eventName, this.handleKeyDown, this);
32850             this.enabled = true;
32851         }
32852     },
32853
32854     /**
32855      * Disable this KeyMap
32856      */
32857     disable: function(){
32858         if(this.enabled){
32859             this.el.removeListener(this.eventName, this.handleKeyDown, this);
32860             this.enabled = false;
32861         }
32862     },
32863
32864     /**
32865      * Convenience function for setting disabled/enabled by boolean.
32866      * @param {Boolean} disabled
32867      */
32868     setDisabled : function(disabled){
32869         if (disabled) {
32870             this.disable();
32871         } else {
32872             this.enable();
32873         }
32874     },
32875     
32876     /**
32877      * Destroys the KeyMap instance and removes all handlers.
32878      * @param {Boolean} removeEl True to also remove the attached element
32879      */
32880     destroy: function(removeEl){
32881         var me = this;
32882         
32883         me.bindings = [];
32884         me.disable();
32885         if (removeEl === true) {
32886             me.el.remove();
32887         }
32888         delete me.el;
32889     }
32890 });
32891 /**
32892  * @class Ext.util.ClickRepeater
32893  * @extends Ext.util.Observable
32894  *
32895  * A wrapper class which can be applied to any element. Fires a "click" event while the
32896  * mouse is pressed. The interval between firings may be specified in the config but
32897  * defaults to 20 milliseconds.
32898  *
32899  * Optionally, a CSS class may be applied to the element during the time it is pressed.
32900  *
32901  * @constructor
32902  * @param {Mixed} el The element to listen on
32903  * @param {Object} config
32904  */
32905
32906 Ext.define('Ext.util.ClickRepeater', {
32907     extend: 'Ext.util.Observable',
32908
32909     constructor : function(el, config){
32910         this.el = Ext.get(el);
32911         this.el.unselectable();
32912
32913         Ext.apply(this, config);
32914
32915         this.addEvents(
32916         /**
32917          * @event mousedown
32918          * Fires when the mouse button is depressed.
32919          * @param {Ext.util.ClickRepeater} this
32920          * @param {Ext.EventObject} e
32921          */
32922         "mousedown",
32923         /**
32924          * @event click
32925          * Fires on a specified interval during the time the element is pressed.
32926          * @param {Ext.util.ClickRepeater} this
32927          * @param {Ext.EventObject} e
32928          */
32929         "click",
32930         /**
32931          * @event mouseup
32932          * Fires when the mouse key is released.
32933          * @param {Ext.util.ClickRepeater} this
32934          * @param {Ext.EventObject} e
32935          */
32936         "mouseup"
32937         );
32938
32939         if(!this.disabled){
32940             this.disabled = true;
32941             this.enable();
32942         }
32943
32944         // allow inline handler
32945         if(this.handler){
32946             this.on("click", this.handler,  this.scope || this);
32947         }
32948
32949         this.callParent();
32950     },
32951
32952     /**
32953      * @cfg {Mixed} el The element to act as a button.
32954      */
32955
32956     /**
32957      * @cfg {String} pressedCls A CSS class name to be applied to the element while pressed.
32958      */
32959
32960     /**
32961      * @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
32962      * "interval" and "delay" are ignored.
32963      */
32964
32965     /**
32966      * @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
32967      */
32968     interval : 20,
32969
32970     /**
32971      * @cfg {Number} delay The initial delay before the repeating event begins firing.
32972      * Similar to an autorepeat key delay.
32973      */
32974     delay: 250,
32975
32976     /**
32977      * @cfg {Boolean} preventDefault True to prevent the default click event
32978      */
32979     preventDefault : true,
32980     /**
32981      * @cfg {Boolean} stopDefault True to stop the default click event
32982      */
32983     stopDefault : false,
32984
32985     timer : 0,
32986
32987     /**
32988      * Enables the repeater and allows events to fire.
32989      */
32990     enable: function(){
32991         if(this.disabled){
32992             this.el.on('mousedown', this.handleMouseDown, this);
32993             if (Ext.isIE){
32994                 this.el.on('dblclick', this.handleDblClick, this);
32995             }
32996             if(this.preventDefault || this.stopDefault){
32997                 this.el.on('click', this.eventOptions, this);
32998             }
32999         }
33000         this.disabled = false;
33001     },
33002
33003     /**
33004      * Disables the repeater and stops events from firing.
33005      */
33006     disable: function(/* private */ force){
33007         if(force || !this.disabled){
33008             clearTimeout(this.timer);
33009             if(this.pressedCls){
33010                 this.el.removeCls(this.pressedCls);
33011             }
33012             Ext.getDoc().un('mouseup', this.handleMouseUp, this);
33013             this.el.removeAllListeners();
33014         }
33015         this.disabled = true;
33016     },
33017
33018     /**
33019      * Convenience function for setting disabled/enabled by boolean.
33020      * @param {Boolean} disabled
33021      */
33022     setDisabled: function(disabled){
33023         this[disabled ? 'disable' : 'enable']();
33024     },
33025
33026     eventOptions: function(e){
33027         if(this.preventDefault){
33028             e.preventDefault();
33029         }
33030         if(this.stopDefault){
33031             e.stopEvent();
33032         }
33033     },
33034
33035     // private
33036     destroy : function() {
33037         this.disable(true);
33038         Ext.destroy(this.el);
33039         this.clearListeners();
33040     },
33041
33042     handleDblClick : function(e){
33043         clearTimeout(this.timer);
33044         this.el.blur();
33045
33046         this.fireEvent("mousedown", this, e);
33047         this.fireEvent("click", this, e);
33048     },
33049
33050     // private
33051     handleMouseDown : function(e){
33052         clearTimeout(this.timer);
33053         this.el.blur();
33054         if(this.pressedCls){
33055             this.el.addCls(this.pressedCls);
33056         }
33057         this.mousedownTime = new Date();
33058
33059         Ext.getDoc().on("mouseup", this.handleMouseUp, this);
33060         this.el.on("mouseout", this.handleMouseOut, this);
33061
33062         this.fireEvent("mousedown", this, e);
33063         this.fireEvent("click", this, e);
33064
33065         // Do not honor delay or interval if acceleration wanted.
33066         if (this.accelerate) {
33067             this.delay = 400;
33068         }
33069
33070         // Re-wrap the event object in a non-shared object, so it doesn't lose its context if
33071         // the global shared EventObject gets a new Event put into it before the timer fires.
33072         e = new Ext.EventObjectImpl(e);
33073
33074         this.timer =  Ext.defer(this.click, this.delay || this.interval, this, [e]);
33075     },
33076
33077     // private
33078     click : function(e){
33079         this.fireEvent("click", this, e);
33080         this.timer =  Ext.defer(this.click, this.accelerate ?
33081             this.easeOutExpo(Ext.Date.getElapsed(this.mousedownTime),
33082                 400,
33083                 -390,
33084                 12000) :
33085             this.interval, this, [e]);
33086     },
33087
33088     easeOutExpo : function (t, b, c, d) {
33089         return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
33090     },
33091
33092     // private
33093     handleMouseOut : function(){
33094         clearTimeout(this.timer);
33095         if(this.pressedCls){
33096             this.el.removeCls(this.pressedCls);
33097         }
33098         this.el.on("mouseover", this.handleMouseReturn, this);
33099     },
33100
33101     // private
33102     handleMouseReturn : function(){
33103         this.el.un("mouseover", this.handleMouseReturn, this);
33104         if(this.pressedCls){
33105             this.el.addCls(this.pressedCls);
33106         }
33107         this.click();
33108     },
33109
33110     // private
33111     handleMouseUp : function(e){
33112         clearTimeout(this.timer);
33113         this.el.un("mouseover", this.handleMouseReturn, this);
33114         this.el.un("mouseout", this.handleMouseOut, this);
33115         Ext.getDoc().un("mouseup", this.handleMouseUp, this);
33116         if(this.pressedCls){
33117             this.el.removeCls(this.pressedCls);
33118         }
33119         this.fireEvent("mouseup", this, e);
33120     }
33121 });
33122
33123 /**
33124  * Component layout for buttons
33125  * @class Ext.layout.component.Button
33126  * @extends Ext.layout.component.Component
33127  * @private
33128  */
33129 Ext.define('Ext.layout.component.Button', {
33130
33131     /* Begin Definitions */
33132
33133     alias: ['layout.button'],
33134
33135     extend: 'Ext.layout.component.Component',
33136
33137     /* End Definitions */
33138
33139     type: 'button',
33140
33141     cellClsRE: /-btn-(tl|br)\b/,
33142     htmlRE: /<.*>/,
33143
33144     beforeLayout: function() {
33145         return this.callParent(arguments) || this.lastText !== this.owner.text;
33146     },
33147
33148     /**
33149      * Set the dimensions of the inner &lt;button&gt; element to match the
33150      * component dimensions.
33151      */
33152     onLayout: function(width, height) {
33153         var me = this,
33154             isNum = Ext.isNumber,
33155             owner = me.owner,
33156             ownerEl = owner.el,
33157             btnEl = owner.btnEl,
33158             btnInnerEl = owner.btnInnerEl,
33159             minWidth = owner.minWidth,
33160             maxWidth = owner.maxWidth,
33161             ownerWidth, btnFrameWidth, metrics;
33162
33163         me.getTargetInfo();
33164         me.callParent(arguments);
33165
33166         btnInnerEl.unclip();
33167         me.setTargetSize(width, height);
33168
33169         if (!isNum(width)) {
33170             // In IE7 strict mode button elements with width:auto get strange extra side margins within
33171             // the wrapping table cell, but they go away if the width is explicitly set. So we measure
33172             // the size of the text and set the width to match.
33173             if (owner.text && Ext.isIE7 && Ext.isStrict && btnEl && btnEl.getWidth() > 20) {
33174                 btnFrameWidth = me.btnFrameWidth;
33175                 metrics = Ext.util.TextMetrics.measure(btnInnerEl, owner.text);
33176                 ownerEl.setWidth(metrics.width + btnFrameWidth + me.adjWidth);
33177                 btnEl.setWidth(metrics.width + btnFrameWidth);
33178                 btnInnerEl.setWidth(metrics.width + btnFrameWidth);
33179             } else {
33180                 // Remove any previous fixed widths
33181                 ownerEl.setWidth(null);
33182                 btnEl.setWidth(null);
33183                 btnInnerEl.setWidth(null);
33184             }
33185
33186             // Handle maxWidth/minWidth config
33187             if (minWidth || maxWidth) {
33188                 ownerWidth = ownerEl.getWidth();
33189                 if (minWidth && (ownerWidth < minWidth)) {
33190                     me.setTargetSize(minWidth, height);
33191                 }
33192                 else if (maxWidth && (ownerWidth > maxWidth)) {
33193                     btnInnerEl.clip();
33194                     me.setTargetSize(maxWidth, height);
33195                 }
33196             }
33197         }
33198
33199         this.lastText = owner.text;
33200     },
33201
33202     setTargetSize: function(width, height) {
33203         var me = this,
33204             owner = me.owner,
33205             isNum = Ext.isNumber,
33206             btnInnerEl = owner.btnInnerEl,
33207             btnWidth = (isNum(width) ? width - me.adjWidth : width),
33208             btnHeight = (isNum(height) ? height - me.adjHeight : height),
33209             btnFrameHeight = me.btnFrameHeight,
33210             text = owner.getText(),
33211             textHeight;
33212
33213         me.callParent(arguments);
33214         me.setElementSize(owner.btnEl, btnWidth, btnHeight);
33215         me.setElementSize(btnInnerEl, btnWidth, btnHeight);
33216         if (isNum(btnHeight)) {
33217             btnInnerEl.setStyle('line-height', btnHeight - btnFrameHeight + 'px');
33218         }
33219
33220         // Button text may contain markup that would force it to wrap to more than one line (e.g. 'Button<br>Label').
33221         // When this happens, we cannot use the line-height set above for vertical centering; we instead reset the
33222         // line-height to normal, measure the rendered text height, and add padding-top to center the text block
33223         // vertically within the button's height. This is more expensive than the basic line-height approach so
33224         // we only do it if the text contains markup.
33225         if (text && this.htmlRE.test(text)) {
33226             btnInnerEl.setStyle('line-height', 'normal');
33227             textHeight = Ext.util.TextMetrics.measure(btnInnerEl, text).height;
33228             btnInnerEl.setStyle('padding-top', me.btnFrameTop + Math.max(btnInnerEl.getHeight() - btnFrameHeight - textHeight, 0) / 2 + 'px');
33229             me.setElementSize(btnInnerEl, btnWidth, btnHeight);
33230         }
33231     },
33232
33233     getTargetInfo: function() {
33234         var me = this,
33235             owner = me.owner,
33236             ownerEl = owner.el,
33237             frameSize = me.frameSize,
33238             frameBody = owner.frameBody,
33239             btnWrap = owner.btnWrap,
33240             innerEl = owner.btnInnerEl;
33241
33242         if (!('adjWidth' in me)) {
33243             Ext.apply(me, {
33244                 // Width adjustment must take into account the arrow area. The btnWrap is the <em> which has padding to accommodate the arrow.
33245                 adjWidth: frameSize.left + frameSize.right + ownerEl.getBorderWidth('lr') + ownerEl.getPadding('lr') +
33246                           btnWrap.getPadding('lr') + (frameBody ? frameBody.getFrameWidth('lr') : 0),
33247                 adjHeight: frameSize.top + frameSize.bottom + ownerEl.getBorderWidth('tb') + ownerEl.getPadding('tb') +
33248                            btnWrap.getPadding('tb') + (frameBody ? frameBody.getFrameWidth('tb') : 0),
33249                 btnFrameWidth: innerEl.getFrameWidth('lr'),
33250                 btnFrameHeight: innerEl.getFrameWidth('tb'),
33251                 btnFrameTop: innerEl.getFrameWidth('t')
33252             });
33253         }
33254
33255         return me.callParent();
33256     }
33257 });
33258 /**
33259  * @class Ext.util.TextMetrics
33260  * <p>
33261  * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
33262  * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
33263  * should not contain any HTML, otherwise it may not be measured correctly.</p> 
33264  * <p>The measurement works by copying the relevant CSS styles that can affect the font related display, 
33265  * then checking the size of an element that is auto-sized. Note that if the text is multi-lined, you must 
33266  * provide a <b>fixed width</b> when doing the measurement.</p>
33267  * 
33268  * <p>
33269  * If multiple measurements are being done on the same element, you create a new instance to initialize 
33270  * to avoid the overhead of copying the styles to the element repeatedly.
33271  * </p>
33272  */
33273 Ext.define('Ext.util.TextMetrics', {
33274     statics: {
33275         shared: null,
33276         /**
33277          * Measures the size of the specified text
33278          * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
33279          * that can affect the size of the rendered text
33280          * @param {String} text The text to measure
33281          * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
33282          * in order to accurately measure the text height
33283          * @return {Object} An object containing the text's size {width: (width), height: (height)}
33284          */
33285         measure: function(el, text, fixedWidth){
33286             var me = this,
33287                 shared = me.shared;
33288             
33289             if(!shared){
33290                 shared = me.shared = new me(el, fixedWidth);
33291             }
33292             shared.bind(el);
33293             shared.setFixedWidth(fixedWidth || 'auto');
33294             return shared.getSize(text);
33295         },
33296         
33297         /**
33298           * Destroy the TextMetrics instance created by {@link #measure}.
33299           */
33300          destroy: function(){
33301              var me = this;
33302              Ext.destroy(me.shared);
33303              me.shared = null;
33304          }
33305     },
33306     
33307     /**
33308      * @constructor
33309      * @param {Mixed} bindTo The element to bind to.
33310      * @param {Number} fixedWidth A fixed width to apply to the measuring element.
33311      */
33312     constructor: function(bindTo, fixedWidth){
33313         var measure = this.measure = Ext.getBody().createChild({
33314             cls: 'x-textmetrics'
33315         });
33316         this.el = Ext.get(bindTo);
33317         
33318         measure.position('absolute');
33319         measure.setLeftTop(-1000, -1000);
33320         measure.hide();
33321
33322         if (fixedWidth) {
33323            measure.setWidth(fixedWidth);
33324         }
33325     },
33326     
33327     /**
33328      * <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
33329      * Returns the size of the specified text based on the internal element's style and width properties
33330      * @param {String} text The text to measure
33331      * @return {Object} An object containing the text's size {width: (width), height: (height)}
33332      */
33333     getSize: function(text){
33334         var measure = this.measure,
33335             size;
33336         
33337         measure.update(text);
33338         size = measure.getSize();
33339         measure.update('');
33340         return size;
33341     },
33342     
33343     /**
33344      * Binds this TextMetrics instance to a new element
33345      * @param {Mixed} el The element
33346      */
33347     bind: function(el){
33348         var me = this;
33349         
33350         me.el = Ext.get(el);
33351         me.measure.setStyle(
33352             me.el.getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
33353         );
33354     },
33355     
33356     /**
33357      * Sets a fixed width on the internal measurement element.  If the text will be multiline, you have
33358      * to set a fixed width in order to accurately measure the text height.
33359      * @param {Number} width The width to set on the element
33360      */
33361      setFixedWidth : function(width){
33362          this.measure.setWidth(width);
33363      },
33364      
33365      /**
33366       * Returns the measured width of the specified text
33367       * @param {String} text The text to measure
33368       * @return {Number} width The width in pixels
33369       */
33370      getWidth : function(text){
33371          this.measure.dom.style.width = 'auto';
33372          return this.getSize(text).width;
33373      },
33374      
33375      /**
33376       * Returns the measured height of the specified text
33377       * @param {String} text The text to measure
33378       * @return {Number} height The height in pixels
33379       */
33380      getHeight : function(text){
33381          return this.getSize(text).height;
33382      },
33383      
33384      /**
33385       * Destroy this instance
33386       */
33387      destroy: function(){
33388          var me = this;
33389          me.measure.remove();
33390          delete me.el;
33391          delete me.measure;
33392      }
33393 }, function(){
33394     Ext.core.Element.addMethods({
33395         /**
33396          * Returns the width in pixels of the passed text, or the width of the text in this Element.
33397          * @param {String} text The text to measure. Defaults to the innerHTML of the element.
33398          * @param {Number} min (Optional) The minumum value to return.
33399          * @param {Number} max (Optional) The maximum value to return.
33400          * @return {Number} The text width in pixels.
33401          * @member Ext.core.Element getTextWidth
33402          */
33403         getTextWidth : function(text, min, max){
33404             return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000);
33405         }
33406     });
33407 });
33408
33409 /**
33410  * @class Ext.layout.container.boxOverflow.Scroller
33411  * @extends Ext.layout.container.boxOverflow.None
33412  * @private
33413  */
33414 Ext.define('Ext.layout.container.boxOverflow.Scroller', {
33415
33416     /* Begin Definitions */
33417
33418     extend: 'Ext.layout.container.boxOverflow.None',
33419     requires: ['Ext.util.ClickRepeater', 'Ext.core.Element'],
33420     alternateClassName: 'Ext.layout.boxOverflow.Scroller',
33421     mixins: {
33422         observable: 'Ext.util.Observable'
33423     },
33424     
33425     /* End Definitions */
33426
33427     /**
33428      * @cfg {Boolean} animateScroll
33429      * True to animate the scrolling of items within the layout (defaults to true, ignored if enableScroll is false)
33430      */
33431     animateScroll: false,
33432
33433     /**
33434      * @cfg {Number} scrollIncrement
33435      * The number of pixels to scroll by on scroller click (defaults to 24)
33436      */
33437     scrollIncrement: 20,
33438
33439     /**
33440      * @cfg {Number} wheelIncrement
33441      * The number of pixels to increment on mouse wheel scrolling (defaults to <tt>3</tt>).
33442      */
33443     wheelIncrement: 10,
33444
33445     /**
33446      * @cfg {Number} scrollRepeatInterval
33447      * Number of milliseconds between each scroll while a scroller button is held down (defaults to 20)
33448      */
33449     scrollRepeatInterval: 60,
33450
33451     /**
33452      * @cfg {Number} scrollDuration
33453      * Number of milliseconds that each scroll animation lasts (defaults to 400)
33454      */
33455     scrollDuration: 400,
33456
33457     /**
33458      * @cfg {String} beforeCtCls
33459      * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
33460      * which must always be present at the leftmost edge of the Container
33461      */
33462
33463     /**
33464      * @cfg {String} afterCtCls
33465      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
33466      * which must always be present at the rightmost edge of the Container
33467      */
33468
33469     /**
33470      * @cfg {String} scrollerCls
33471      * CSS class added to both scroller elements if enableScroll is used
33472      */
33473     scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
33474
33475     /**
33476      * @cfg {String} beforeScrollerCls
33477      * CSS class added to the left scroller element if enableScroll is used
33478      */
33479
33480     /**
33481      * @cfg {String} afterScrollerCls
33482      * CSS class added to the right scroller element if enableScroll is used
33483      */
33484     
33485     constructor: function(layout, config) {
33486         this.layout = layout;
33487         Ext.apply(this, config || {});
33488         
33489         this.addEvents(
33490             /**
33491              * @event scroll
33492              * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
33493              * @param {Number} newPosition The new position of the scroller
33494              * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
33495              */
33496             'scroll'
33497         );
33498     },
33499     
33500     initCSSClasses: function() {
33501         var me = this,
33502         layout = me.layout;
33503
33504         if (!me.CSSinitialized) {
33505             me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
33506             me.afterCtCls  = me.afterCtCls  || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
33507             me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
33508             me.afterScrollerCls  = me.afterScrollerCls  || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
33509             me.CSSinitializes = true;
33510         }
33511     },
33512
33513     handleOverflow: function(calculations, targetSize) {
33514         var me = this,
33515             layout = me.layout,
33516             methodName = 'get' + layout.parallelPrefixCap,
33517             newSize = {};
33518
33519         me.initCSSClasses();
33520         me.callParent(arguments);
33521         this.createInnerElements();
33522         this.showScrollers();
33523         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
33524         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
33525         return { targetSize: newSize };
33526     },
33527
33528     /**
33529      * @private
33530      * Creates the beforeCt and afterCt elements if they have not already been created
33531      */
33532     createInnerElements: function() {
33533         var me = this,
33534             target = me.layout.getRenderTarget();
33535
33536         //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
33537         //special items such as scrollers or dropdown menu triggers
33538         if (!me.beforeCt) {
33539             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
33540             me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
33541             me.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls},  'after');
33542             me.createWheelListener();
33543         }
33544     },
33545
33546     /**
33547      * @private
33548      * Sets up an listener to scroll on the layout's innerCt mousewheel event
33549      */
33550     createWheelListener: function() {
33551         this.layout.innerCt.on({
33552             scope     : this,
33553             mousewheel: function(e) {
33554                 e.stopEvent();
33555
33556                 this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
33557             }
33558         });
33559     },
33560
33561     /**
33562      * @private
33563      */
33564     clearOverflow: function() {
33565         this.hideScrollers();
33566     },
33567
33568     /**
33569      * @private
33570      * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
33571      * present. 
33572      */
33573     showScrollers: function() {
33574         this.createScrollers();
33575         this.beforeScroller.show();
33576         this.afterScroller.show();
33577         this.updateScrollButtons();
33578         
33579         this.layout.owner.addClsWithUI('scroller');
33580     },
33581
33582     /**
33583      * @private
33584      * Hides the scroller elements in the beforeCt and afterCt
33585      */
33586     hideScrollers: function() {
33587         if (this.beforeScroller != undefined) {
33588             this.beforeScroller.hide();
33589             this.afterScroller.hide();
33590             
33591             this.layout.owner.removeClsWithUI('scroller');
33592         }
33593     },
33594
33595     /**
33596      * @private
33597      * Creates the clickable scroller elements and places them into the beforeCt and afterCt
33598      */
33599     createScrollers: function() {
33600         if (!this.beforeScroller && !this.afterScroller) {
33601             var before = this.beforeCt.createChild({
33602                 cls: Ext.String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
33603             });
33604
33605             var after = this.afterCt.createChild({
33606                 cls: Ext.String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
33607             });
33608
33609             before.addClsOnOver(this.beforeScrollerCls + '-hover');
33610             after.addClsOnOver(this.afterScrollerCls + '-hover');
33611
33612             before.setVisibilityMode(Ext.core.Element.DISPLAY);
33613             after.setVisibilityMode(Ext.core.Element.DISPLAY);
33614
33615             this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
33616                 interval: this.scrollRepeatInterval,
33617                 handler : this.scrollLeft,
33618                 scope   : this
33619             });
33620
33621             this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
33622                 interval: this.scrollRepeatInterval,
33623                 handler : this.scrollRight,
33624                 scope   : this
33625             });
33626
33627             /**
33628              * @property beforeScroller
33629              * @type Ext.core.Element
33630              * The left scroller element. Only created when needed.
33631              */
33632             this.beforeScroller = before;
33633
33634             /**
33635              * @property afterScroller
33636              * @type Ext.core.Element
33637              * The left scroller element. Only created when needed.
33638              */
33639             this.afterScroller = after;
33640         }
33641     },
33642
33643     /**
33644      * @private
33645      */
33646     destroy: function() {
33647         Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
33648     },
33649
33650     /**
33651      * @private
33652      * Scrolls left or right by the number of pixels specified
33653      * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
33654      */
33655     scrollBy: function(delta, animate) {
33656         this.scrollTo(this.getScrollPosition() + delta, animate);
33657     },
33658
33659     /**
33660      * @private
33661      * @return {Object} Object passed to scrollTo when scrolling
33662      */
33663     getScrollAnim: function() {
33664         return {
33665             duration: this.scrollDuration, 
33666             callback: this.updateScrollButtons, 
33667             scope   : this
33668         };
33669     },
33670
33671     /**
33672      * @private
33673      * Enables or disables each scroller button based on the current scroll position
33674      */
33675     updateScrollButtons: function() {
33676         if (this.beforeScroller == undefined || this.afterScroller == undefined) {
33677             return;
33678         }
33679
33680         var beforeMeth = this.atExtremeBefore()  ? 'addCls' : 'removeCls',
33681             afterMeth  = this.atExtremeAfter() ? 'addCls' : 'removeCls',
33682             beforeCls  = this.beforeScrollerCls + '-disabled',
33683             afterCls   = this.afterScrollerCls  + '-disabled';
33684         
33685         this.beforeScroller[beforeMeth](beforeCls);
33686         this.afterScroller[afterMeth](afterCls);
33687         this.scrolling = false;
33688     },
33689
33690     /**
33691      * @private
33692      * Returns true if the innerCt scroll is already at its left-most point
33693      * @return {Boolean} True if already at furthest left point
33694      */
33695     atExtremeBefore: function() {
33696         return this.getScrollPosition() === 0;
33697     },
33698
33699     /**
33700      * @private
33701      * Scrolls to the left by the configured amount
33702      */
33703     scrollLeft: function() {
33704         this.scrollBy(-this.scrollIncrement, false);
33705     },
33706
33707     /**
33708      * @private
33709      * Scrolls to the right by the configured amount
33710      */
33711     scrollRight: function() {
33712         this.scrollBy(this.scrollIncrement, false);
33713     },
33714
33715     /**
33716      * Returns the current scroll position of the innerCt element
33717      * @return {Number} The current scroll position
33718      */
33719     getScrollPosition: function(){
33720         var layout = this.layout;
33721         return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
33722     },
33723
33724     /**
33725      * @private
33726      * Returns the maximum value we can scrollTo
33727      * @return {Number} The max scroll value
33728      */
33729     getMaxScrollPosition: function() {
33730         var layout = this.layout;
33731         return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
33732     },
33733
33734     /**
33735      * @private
33736      * Returns true if the innerCt scroll is already at its right-most point
33737      * @return {Boolean} True if already at furthest right point
33738      */
33739     atExtremeAfter: function() {
33740         return this.getScrollPosition() >= this.getMaxScrollPosition();
33741     },
33742
33743     /**
33744      * @private
33745      * Scrolls to the given position. Performs bounds checking.
33746      * @param {Number} position The position to scroll to. This is constrained.
33747      * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
33748      */
33749     scrollTo: function(position, animate) {
33750         var me = this,
33751             layout = me.layout,
33752             oldPosition = me.getScrollPosition(),
33753             newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
33754
33755         if (newPosition != oldPosition && !me.scrolling) {
33756             if (animate == undefined) {
33757                 animate = me.animateScroll;
33758             }
33759
33760             layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
33761             if (animate) {
33762                 me.scrolling = true;
33763             } else {
33764                 me.scrolling = false;
33765                 me.updateScrollButtons();
33766             }
33767             
33768             me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
33769         }
33770     },
33771
33772     /**
33773      * Scrolls to the given component.
33774      * @param {String|Number|Ext.Component} item The item to scroll to. Can be a numerical index, component id 
33775      * or a reference to the component itself.
33776      * @param {Boolean} animate True to animate the scrolling
33777      */
33778     scrollToItem: function(item, animate) {
33779         var me = this,
33780             layout = me.layout,
33781             visibility,
33782             box,
33783             newPos;
33784
33785         item = me.getItem(item);
33786         if (item != undefined) {
33787             visibility = this.getItemVisibility(item);
33788             if (!visibility.fullyVisible) {
33789                 box  = item.getBox(true, true);
33790                 newPos = box[layout.parallelPosition];
33791                 if (visibility.hiddenEnd) {
33792                     newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
33793                 }
33794                 this.scrollTo(newPos, animate);
33795             }
33796         }
33797     },
33798
33799     /**
33800      * @private
33801      * For a given item in the container, return an object with information on whether the item is visible
33802      * with the current innerCt scroll value.
33803      * @param {Ext.Component} item The item
33804      * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
33805      */
33806     getItemVisibility: function(item) {
33807         var me          = this,
33808             box         = me.getItem(item).getBox(true, true),
33809             layout      = me.layout,
33810             itemStart   = box[layout.parallelPosition],
33811             itemEnd     = itemStart + box[layout.parallelPrefix],
33812             scrollStart = me.getScrollPosition(),
33813             scrollEnd   = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
33814
33815         return {
33816             hiddenStart : itemStart < scrollStart,
33817             hiddenEnd   : itemEnd > scrollEnd,
33818             fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd
33819         };
33820     }
33821 });
33822 /**
33823  * @class Ext.util.Offset
33824  * @ignore
33825  */
33826 Ext.define('Ext.util.Offset', {
33827
33828     /* Begin Definitions */
33829
33830     statics: {
33831         fromObject: function(obj) {
33832             return new this(obj.x, obj.y);
33833         }
33834     },
33835
33836     /* End Definitions */
33837
33838     constructor: function(x, y) {
33839         this.x = (x != null && !isNaN(x)) ? x : 0;
33840         this.y = (y != null && !isNaN(y)) ? y : 0;
33841
33842         return this;
33843     },
33844
33845     copy: function() {
33846         return new Ext.util.Offset(this.x, this.y);
33847     },
33848
33849     copyFrom: function(p) {
33850         this.x = p.x;
33851         this.y = p.y;
33852     },
33853
33854     toString: function() {
33855         return "Offset[" + this.x + "," + this.y + "]";
33856     },
33857
33858     equals: function(offset) {
33859         if(!(offset instanceof this.statics())) {
33860             Ext.Error.raise('Offset must be an instance of Ext.util.Offset');
33861         }
33862
33863         return (this.x == offset.x && this.y == offset.y);
33864     },
33865
33866     round: function(to) {
33867         if (!isNaN(to)) {
33868             var factor = Math.pow(10, to);
33869             this.x = Math.round(this.x * factor) / factor;
33870             this.y = Math.round(this.y * factor) / factor;
33871         } else {
33872             this.x = Math.round(this.x);
33873             this.y = Math.round(this.y);
33874         }
33875     },
33876
33877     isZero: function() {
33878         return this.x == 0 && this.y == 0;
33879     }
33880 });
33881
33882 /**
33883  * @class Ext.util.KeyNav
33884  * <p>Provides a convenient wrapper for normalized keyboard navigation.  KeyNav allows you to bind
33885  * navigation keys to function calls that will get called when the keys are pressed, providing an easy
33886  * way to implement custom navigation schemes for any UI component.</p>
33887  * <p>The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,
33888  * pageUp, pageDown, del, backspace, home, end.  Usage:</p>
33889  <pre><code>
33890 var nav = new Ext.util.KeyNav("my-element", {
33891     "left" : function(e){
33892         this.moveLeft(e.ctrlKey);
33893     },
33894     "right" : function(e){
33895         this.moveRight(e.ctrlKey);
33896     },
33897     "enter" : function(e){
33898         this.save();
33899     },
33900     scope : this
33901 });
33902 </code></pre>
33903  * @constructor
33904  * @param {Mixed} el The element to bind to
33905  * @param {Object} config The config
33906  */
33907 Ext.define('Ext.util.KeyNav', {
33908     
33909     alternateClassName: 'Ext.KeyNav',
33910     
33911     requires: ['Ext.util.KeyMap'],
33912     
33913     statics: {
33914         keyOptions: {
33915             left: 37,
33916             right: 39,
33917             up: 38,
33918             down: 40,
33919             space: 32,
33920             pageUp: 33,
33921             pageDown: 34,
33922             del: 46,
33923             backspace: 8,
33924             home: 36,
33925             end: 35,
33926             enter: 13,
33927             esc: 27,
33928             tab: 9
33929         }
33930     },
33931     
33932     constructor: function(el, config){
33933         this.setConfig(el, config || {});
33934     },
33935     
33936     /**
33937      * Sets up a configuration for the KeyNav.
33938      * @private
33939      * @param {Mixed} el The element to bind to
33940      * @param {Object}A configuration object as specified in the constructor.
33941      */
33942     setConfig: function(el, config) {
33943         if (this.map) {
33944             this.map.destroy();
33945         }
33946         
33947         var map = Ext.create('Ext.util.KeyMap', el, null, this.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : this.forceKeyDown)),
33948             keys = Ext.util.KeyNav.keyOptions,
33949             scope = config.scope || this,
33950             key;
33951         
33952         this.map = map;
33953         for (key in keys) {
33954             if (keys.hasOwnProperty(key)) {
33955                 if (config[key]) {
33956                     map.addBinding({
33957                         scope: scope,
33958                         key: keys[key],
33959                         handler: Ext.Function.bind(this.handleEvent, scope, [config[key]], true),
33960                         defaultEventAction: config.defaultEventAction || this.defaultEventAction
33961                     });
33962                 }
33963             }
33964         }
33965         
33966         map.disable();
33967         if (!config.disabled) {
33968             map.enable();
33969         }
33970     },
33971     
33972     /**
33973      * Method for filtering out the map argument
33974      * @private
33975      * @param {Ext.util.KeyMap} map
33976      * @param {Ext.EventObject} event
33977      * @param {Object} options Contains the handler to call
33978      */
33979     handleEvent: function(map, event, handler){
33980         return handler.call(this, event);
33981     },
33982     
33983     /**
33984      * @cfg {Boolean} disabled
33985      * True to disable this KeyNav instance (defaults to false)
33986      */
33987     disabled: false,
33988     
33989     /**
33990      * @cfg {String} defaultEventAction
33991      * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key.  Valid values are
33992      * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
33993      * {@link Ext.EventObject#stopPropagation} (defaults to 'stopEvent')
33994      */
33995     defaultEventAction: "stopEvent",
33996     
33997     /**
33998      * @cfg {Boolean} forceKeyDown
33999      * Handle the keydown event instead of keypress (defaults to false).  KeyNav automatically does this for IE since
34000      * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
34001      * handle keydown instead of keypress.
34002      */
34003     forceKeyDown: false,
34004     
34005     /**
34006      * Destroy this KeyNav (this is the same as calling disable).
34007      * @param {Boolean} removeEl True to remove the element associated with this KeyNav.
34008      */
34009     destroy: function(removeEl){
34010         this.map.destroy(removeEl);
34011         delete this.map;
34012     },
34013
34014     /**
34015      * Enable this KeyNav
34016      */
34017     enable: function() {
34018         this.map.enable();
34019         this.disabled = false;
34020     },
34021
34022     /**
34023      * Disable this KeyNav
34024      */
34025     disable: function() {
34026         this.map.disable();
34027         this.disabled = true;
34028     },
34029     
34030     /**
34031      * Convenience function for setting disabled/enabled by boolean.
34032      * @param {Boolean} disabled
34033      */
34034     setDisabled : function(disabled){
34035         this.map.setDisabled(disabled);
34036         this.disabled = disabled;
34037     },
34038     
34039     /**
34040      * Determines the event to bind to listen for keys. Depends on the {@link #forceKeyDown} setting,
34041      * as well as the useKeyDown option on the EventManager.
34042      * @return {String} The type of event to listen for.
34043      */
34044     getKeyEvent: function(forceKeyDown){
34045         return (forceKeyDown || Ext.EventManager.useKeyDown) ? 'keydown' : 'keypress';
34046     }
34047 });
34048
34049 /**
34050  * @class Ext.fx.Queue
34051  * Animation Queue mixin to handle chaining and queueing by target.
34052  * @private
34053  */
34054
34055 Ext.define('Ext.fx.Queue', {
34056
34057     requires: ['Ext.util.HashMap'],
34058
34059     constructor: function() {
34060         this.targets = Ext.create('Ext.util.HashMap');
34061         this.fxQueue = {};
34062     },
34063
34064     // @private
34065     getFxDefaults: function(targetId) {
34066         var target = this.targets.get(targetId);
34067         if (target) {
34068             return target.fxDefaults;
34069         }
34070         return {};
34071     },
34072
34073     // @private
34074     setFxDefaults: function(targetId, obj) {
34075         var target = this.targets.get(targetId);
34076         if (target) {
34077             target.fxDefaults = Ext.apply(target.fxDefaults || {}, obj);
34078         }
34079     },
34080
34081     // @private
34082     stopAnimation: function(targetId) {
34083         var me = this,
34084             queue = me.getFxQueue(targetId),
34085             ln = queue.length;
34086         while (ln) {
34087             queue[ln - 1].end();
34088             ln--;
34089         }
34090     },
34091
34092     /**
34093      * @private
34094      * Returns current animation object if the element has any effects actively running or queued, else returns false.
34095      */
34096     getActiveAnimation: function(targetId) {
34097         var queue = this.getFxQueue(targetId);
34098         return (queue && !!queue.length) ? queue[0] : false;
34099     },
34100
34101     // @private
34102     hasFxBlock: function(targetId) {
34103         var queue = this.getFxQueue(targetId);
34104         return queue && queue[0] && queue[0].block;
34105     },
34106
34107     // @private get fx queue for passed target, create if needed.
34108     getFxQueue: function(targetId) {
34109         if (!targetId) {
34110             return false;
34111         }
34112         var me = this,
34113             queue = me.fxQueue[targetId],
34114             target = me.targets.get(targetId);
34115
34116         if (!target) {
34117             return false;
34118         }
34119
34120         if (!queue) {
34121             me.fxQueue[targetId] = [];
34122             // GarbageCollector will need to clean up Elements since they aren't currently observable
34123             if (target.type != 'element') {
34124                 target.target.on('destroy', function() {
34125                     me.fxQueue[targetId] = [];
34126                 });
34127             }
34128         }
34129         return me.fxQueue[targetId];
34130     },
34131
34132     // @private
34133     queueFx: function(anim) {
34134         var me = this,
34135             target = anim.target,
34136             queue, ln;
34137
34138         if (!target) {
34139             return;
34140         }
34141
34142         queue = me.getFxQueue(target.getId());
34143         ln = queue.length;
34144
34145         if (ln) {
34146             if (anim.concurrent) {
34147                 anim.paused = false;
34148             }
34149             else {
34150                 queue[ln - 1].on('afteranimate', function() {
34151                     anim.paused = false;
34152                 });
34153             }
34154         }
34155         else {
34156             anim.paused = false;
34157         }
34158         anim.on('afteranimate', function() {
34159             Ext.Array.remove(queue, anim);
34160             if (anim.remove) {
34161                 if (target.type == 'element') {
34162                     var el = Ext.get(target.id);
34163                     if (el) {
34164                         el.remove();
34165                     }
34166                 }
34167             }
34168         }, this);
34169         queue.push(anim);
34170     }
34171 });
34172 /**
34173  * @class Ext.fx.target.Target
34174
34175 This class specifies a generic target for an animation. It provides a wrapper around a
34176 series of different types of objects to allow for a generic animation API.
34177 A target can be a single object or a Composite object containing other objects that are 
34178 to be animated. This class and it's subclasses are generally not created directly, the 
34179 underlying animation will create the appropriate Ext.fx.target.Target object by passing 
34180 the instance to be animated.
34181
34182 The following types of objects can be animated:
34183 - {@link #Ext.fx.target.Component Components}
34184 - {@link #Ext.fx.target.Element Elements}
34185 - {@link #Ext.fx.target.Sprite Sprites}
34186
34187  * @markdown
34188  * @abstract
34189  * @constructor
34190  * @param {Mixed} target The object to be animated
34191  */
34192
34193 Ext.define('Ext.fx.target.Target', {
34194
34195     isAnimTarget: true,
34196
34197     constructor: function(target) {
34198         this.target = target;
34199         this.id = this.getId();
34200     },
34201     
34202     getId: function() {
34203         return this.target.id;
34204     }
34205 });
34206
34207 /**
34208  * @class Ext.fx.target.Sprite
34209  * @extends Ext.fx.target.Target
34210
34211 This class represents a animation target for a {@link Ext.draw.Sprite}. In general this class will not be
34212 created directly, the {@link Ext.draw.Sprite} will be passed to the animation and
34213 and the appropriate target will be created.
34214
34215  * @markdown
34216  */
34217
34218 Ext.define('Ext.fx.target.Sprite', {
34219
34220     /* Begin Definitions */
34221
34222     extend: 'Ext.fx.target.Target',
34223
34224     /* End Definitions */
34225
34226     type: 'draw',
34227
34228     getFromPrim: function(sprite, attr) {
34229         var o;
34230         if (attr == 'translate') {
34231             o = {
34232                 x: sprite.attr.translation.x || 0,
34233                 y: sprite.attr.translation.y || 0
34234             };
34235         }
34236         else if (attr == 'rotate') {
34237             o = {
34238                 degrees: sprite.attr.rotation.degrees || 0,
34239                 x: sprite.attr.rotation.x,
34240                 y: sprite.attr.rotation.y
34241             };
34242         }
34243         else {
34244             o = sprite.attr[attr];
34245         }
34246         return o;
34247     },
34248
34249     getAttr: function(attr, val) {
34250         return [[this.target, val != undefined ? val : this.getFromPrim(this.target, attr)]];
34251     },
34252
34253     setAttr: function(targetData) {
34254         var ln = targetData.length,
34255             spriteArr = [],
34256             attrs, attr, attrArr, attPtr, spritePtr, idx, value, i, j, x, y, ln2;
34257         for (i = 0; i < ln; i++) {
34258             attrs = targetData[i].attrs;
34259             for (attr in attrs) {
34260                 attrArr = attrs[attr];
34261                 ln2 = attrArr.length;
34262                 for (j = 0; j < ln2; j++) {
34263                     spritePtr = attrArr[j][0];
34264                     attPtr = attrArr[j][1];
34265                     if (attr === 'translate') {
34266                         value = {
34267                             x: attPtr.x,
34268                             y: attPtr.y
34269                         };
34270                     }
34271                     else if (attr === 'rotate') {
34272                         x = attPtr.x;
34273                         if (isNaN(x)) {
34274                             x = null;
34275                         }
34276                         y = attPtr.y;
34277                         if (isNaN(y)) {
34278                             y = null;
34279                         }
34280                         value = {
34281                             degrees: attPtr.degrees,
34282                             x: x,
34283                             y: y
34284                         };
34285                     }
34286                     else if (attr === 'width' || attr === 'height' || attr === 'x' || attr === 'y') {
34287                         value = parseFloat(attPtr);
34288                     }
34289                     else {
34290                         value = attPtr;
34291                     }
34292                     idx = Ext.Array.indexOf(spriteArr, spritePtr);
34293                     if (idx == -1) {
34294                         spriteArr.push([spritePtr, {}]);
34295                         idx = spriteArr.length - 1;
34296                     }
34297                     spriteArr[idx][1][attr] = value;
34298                 }
34299             }
34300         }
34301         ln = spriteArr.length;
34302         for (i = 0; i < ln; i++) {
34303             spritePtr = spriteArr[i];
34304             spritePtr[0].setAttributes(spritePtr[1]);
34305         }
34306         this.target.redraw();
34307     }
34308 });
34309
34310 /**
34311  * @class Ext.fx.target.CompositeSprite
34312  * @extends Ext.fx.target.Sprite
34313
34314 This class represents a animation target for a {@link Ext.draw.CompositeSprite}. It allows
34315 each {@link Ext.draw.Sprite} in the group to be animated as a whole. In general this class will not be
34316 created directly, the {@link Ext.draw.CompositeSprite} will be passed to the animation and
34317 and the appropriate target will be created.
34318
34319  * @markdown
34320  */
34321
34322 Ext.define('Ext.fx.target.CompositeSprite', {
34323
34324     /* Begin Definitions */
34325
34326     extend: 'Ext.fx.target.Sprite',
34327
34328     /* End Definitions */
34329
34330     getAttr: function(attr, val) {
34331         var out = [],
34332             target = this.target;
34333         target.each(function(sprite) {
34334             out.push([sprite, val != undefined ? val : this.getFromPrim(sprite, attr)]);
34335         }, this);
34336         return out;
34337     }
34338 });
34339
34340 /**
34341  * @class Ext.fx.target.Component
34342  * @extends Ext.fx.target.Target
34343  * 
34344  * This class represents a animation target for a {@link Ext.Component}. In general this class will not be
34345  * created directly, the {@link Ext.Component} will be passed to the animation and
34346  * and the appropriate target will be created.
34347  */
34348 Ext.define('Ext.fx.target.Component', {
34349
34350     /* Begin Definitions */
34351    
34352     extend: 'Ext.fx.target.Target',
34353     
34354     /* End Definitions */
34355
34356     type: 'component',
34357
34358     // Methods to call to retrieve unspecified "from" values from a target Component
34359     getPropMethod: {
34360         top: function() {
34361             return this.getPosition(true)[1];
34362         },
34363         left: function() {
34364             return this.getPosition(true)[0];
34365         },
34366         x: function() {
34367             return this.getPosition()[0];
34368         },
34369         y: function() {
34370             return this.getPosition()[1];
34371         },
34372         height: function() {
34373             return this.getHeight();
34374         },
34375         width: function() {
34376             return this.getWidth();
34377         },
34378         opacity: function() {
34379             return this.el.getStyle('opacity');
34380         }
34381     },
34382
34383     compMethod: {
34384         top: 'setPosition',
34385         left: 'setPosition',
34386         x: 'setPagePosition',
34387         y: 'setPagePosition',
34388         height: 'setSize',
34389         width: 'setSize',
34390         opacity: 'setOpacity'
34391     },
34392
34393     // Read the named attribute from the target Component. Use the defined getter for the attribute
34394     getAttr: function(attr, val) {
34395         return [[this.target, val !== undefined ? val : this.getPropMethod[attr].call(this.target)]];
34396     },
34397
34398     setAttr: function(targetData, isFirstFrame, isLastFrame) {
34399         var me = this,
34400             target = me.target,
34401             ln = targetData.length,
34402             attrs, attr, o, i, j, meth, targets, left, top, w, h;
34403         for (i = 0; i < ln; i++) {
34404             attrs = targetData[i].attrs;
34405             for (attr in attrs) {
34406                 targets = attrs[attr].length;
34407                 meth = {
34408                     setPosition: {},
34409                     setPagePosition: {},
34410                     setSize: {},
34411                     setOpacity: {}
34412                 };
34413                 for (j = 0; j < targets; j++) {
34414                     o = attrs[attr][j];
34415                     // We REALLY want a single function call, so push these down to merge them: eg
34416                     // meth.setPagePosition.target = <targetComponent>
34417                     // meth.setPagePosition['x'] = 100
34418                     // meth.setPagePosition['y'] = 100
34419                     meth[me.compMethod[attr]].target = o[0];
34420                     meth[me.compMethod[attr]][attr] = o[1];
34421                 }
34422                 if (meth.setPosition.target) {
34423                     o = meth.setPosition;
34424                     left = (o.left === undefined) ? undefined : parseInt(o.left, 10);
34425                     top = (o.top === undefined) ? undefined : parseInt(o.top, 10);
34426                     o.target.setPosition(left, top);
34427                 }
34428                 if (meth.setPagePosition.target) {
34429                     o = meth.setPagePosition;
34430                     o.target.setPagePosition(o.x, o.y);
34431                 }
34432                 if (meth.setSize.target) {
34433                     o = meth.setSize;
34434                     // Dimensions not being animated MUST NOT be autosized. They must remain at current value.
34435                     w = (o.width === undefined) ? o.target.getWidth() : parseInt(o.width, 10);
34436                     h = (o.height === undefined) ? o.target.getHeight() : parseInt(o.height, 10);
34437
34438                     // Only set the size of the Component on the last frame, or if the animation was
34439                     // configured with dynamic: true.
34440                     // In other cases, we just set the target element size.
34441                     // This will result in either clipping if animating a reduction in size, or the revealing of
34442                     // the inner elements of the Component if animating an increase in size.
34443                     // Component's animate function initially resizes to the larger size before resizing the
34444                     // outer element to clip the contents.
34445                     if (isLastFrame || me.dynamic) {
34446                         o.target.componentLayout.childrenChanged = true;
34447
34448                         // Flag if we are being called by an animating layout: use setCalculatedSize
34449                         if (me.layoutAnimation) {
34450                             o.target.setCalculatedSize(w, h);
34451                         } else {
34452                             o.target.setSize(w, h);
34453                         }
34454                     }
34455                     else {
34456                         o.target.el.setSize(w, h);
34457                     }
34458                 }
34459                 if (meth.setOpacity.target) {
34460                     o = meth.setOpacity;
34461                     o.target.el.setStyle('opacity', o.opacity);
34462                 }
34463             }
34464         }
34465     }
34466 });
34467
34468 /**
34469  * @class Ext.fx.CubicBezier
34470  * @ignore
34471  */
34472 Ext.define('Ext.fx.CubicBezier', {
34473
34474     /* Begin Definitions */
34475
34476     singleton: true,
34477
34478     /* End Definitions */
34479
34480     cubicBezierAtTime: function(t, p1x, p1y, p2x, p2y, duration) {
34481         var cx = 3 * p1x,
34482             bx = 3 * (p2x - p1x) - cx,
34483             ax = 1 - cx - bx,
34484             cy = 3 * p1y,
34485             by = 3 * (p2y - p1y) - cy,
34486             ay = 1 - cy - by;
34487         function sampleCurveX(t) {
34488             return ((ax * t + bx) * t + cx) * t;
34489         }
34490         function solve(x, epsilon) {
34491             var t = solveCurveX(x, epsilon);
34492             return ((ay * t + by) * t + cy) * t;
34493         }
34494         function solveCurveX(x, epsilon) {
34495             var t0, t1, t2, x2, d2, i;
34496             for (t2 = x, i = 0; i < 8; i++) {
34497                 x2 = sampleCurveX(t2) - x;
34498                 if (Math.abs(x2) < epsilon) {
34499                     return t2;
34500                 }
34501                 d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
34502                 if (Math.abs(d2) < 1e-6) {
34503                     break;
34504                 }
34505                 t2 = t2 - x2 / d2;
34506             }
34507             t0 = 0;
34508             t1 = 1;
34509             t2 = x;
34510             if (t2 < t0) {
34511                 return t0;
34512             }
34513             if (t2 > t1) {
34514                 return t1;
34515             }
34516             while (t0 < t1) {
34517                 x2 = sampleCurveX(t2);
34518                 if (Math.abs(x2 - x) < epsilon) {
34519                     return t2;
34520                 }
34521                 if (x > x2) {
34522                     t0 = t2;
34523                 } else {
34524                     t1 = t2;
34525                 }
34526                 t2 = (t1 - t0) / 2 + t0;
34527             }
34528             return t2;
34529         }
34530         return solve(t, 1 / (200 * duration));
34531     },
34532
34533     cubicBezier: function(x1, y1, x2, y2) {
34534         var fn = function(pos) {
34535             return Ext.fx.CubicBezier.cubicBezierAtTime(pos, x1, y1, x2, y2, 1);
34536         };
34537         fn.toCSS3 = function() {
34538             return 'cubic-bezier(' + [x1, y1, x2, y2].join(',') + ')';
34539         };
34540         fn.reverse = function() {
34541             return Ext.fx.CubicBezier.cubicBezier(1 - x2, 1 - y2, 1 - x1, 1 - y1);
34542         };
34543         return fn;
34544     }
34545 });
34546 /**
34547  * @class Ext.draw.Color
34548  * @extends Object
34549  *
34550  * Represents an RGB color and provides helper functions get
34551  * color components in HSL color space.
34552  */
34553 Ext.define('Ext.draw.Color', {
34554
34555     /* Begin Definitions */
34556
34557     /* End Definitions */
34558
34559     colorToHexRe: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
34560     rgbRe: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
34561     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*/,
34562
34563     /**
34564      * @cfg {Number} lightnessFactor
34565      *
34566      * The default factor to compute the lighter or darker color. Defaults to 0.2.
34567      */
34568     lightnessFactor: 0.2,
34569
34570     /**
34571      * @constructor
34572      * @param {Number} red Red component (0..255)
34573      * @param {Number} green Green component (0..255)
34574      * @param {Number} blue Blue component (0..255)
34575      */
34576     constructor : function(red, green, blue) {
34577         var me = this,
34578             clamp = Ext.Number.constrain;
34579         me.r = clamp(red, 0, 255);
34580         me.g = clamp(green, 0, 255);
34581         me.b = clamp(blue, 0, 255);
34582     },
34583
34584     /**
34585      * Get the red component of the color, in the range 0..255.
34586      * @return {Number}
34587      */
34588     getRed: function() {
34589         return this.r;
34590     },
34591
34592     /**
34593      * Get the green component of the color, in the range 0..255.
34594      * @return {Number}
34595      */
34596     getGreen: function() {
34597         return this.g;
34598     },
34599
34600     /**
34601      * Get the blue component of the color, in the range 0..255.
34602      * @return {Number}
34603      */
34604     getBlue: function() {
34605         return this.b;
34606     },
34607
34608     /**
34609      * Get the RGB values.
34610      * @return {Array}
34611      */
34612     getRGB: function() {
34613         var me = this;
34614         return [me.r, me.g, me.b];
34615     },
34616
34617     /**
34618      * Get the equivalent HSL components of the color.
34619      * @return {Array}
34620      */
34621     getHSL: function() {
34622         var me = this,
34623             r = me.r / 255,
34624             g = me.g / 255,
34625             b = me.b / 255,
34626             max = Math.max(r, g, b),
34627             min = Math.min(r, g, b),
34628             delta = max - min,
34629             h,
34630             s = 0,
34631             l = 0.5 * (max + min);
34632
34633         // min==max means achromatic (hue is undefined)
34634         if (min != max) {
34635             s = (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
34636             if (r == max) {
34637                 h = 60 * (g - b) / delta;
34638             } else if (g == max) {
34639                 h = 120 + 60 * (b - r) / delta;
34640             } else {
34641                 h = 240 + 60 * (r - g) / delta;
34642             }
34643             if (h < 0) {
34644                 h += 360;
34645             }
34646             if (h >= 360) {
34647                 h -= 360;
34648             }
34649         }
34650         return [h, s, l];
34651     },
34652
34653     /**
34654      * Return a new color that is lighter than this color.
34655      * @param {Number} factor Lighter factor (0..1), default to 0.2
34656      * @return Ext.draw.Color
34657      */
34658     getLighter: function(factor) {
34659         var hsl = this.getHSL();
34660         factor = factor || this.lightnessFactor;
34661         hsl[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
34662         return this.fromHSL(hsl[0], hsl[1], hsl[2]);
34663     },
34664
34665     /**
34666      * Return a new color that is darker than this color.
34667      * @param {Number} factor Darker factor (0..1), default to 0.2
34668      * @return Ext.draw.Color
34669      */
34670     getDarker: function(factor) {
34671         factor = factor || this.lightnessFactor;
34672         return this.getLighter(-factor);
34673     },
34674
34675     /**
34676      * Return the color in the hex format, i.e. '#rrggbb'.
34677      * @return {String}
34678      */
34679     toString: function() {
34680         var me = this,
34681             round = Math.round,
34682             r = round(me.r).toString(16),
34683             g = round(me.g).toString(16),
34684             b = round(me.b).toString(16);
34685         r = (r.length == 1) ? '0' + r : r;
34686         g = (g.length == 1) ? '0' + g : g;
34687         b = (b.length == 1) ? '0' + b : b;
34688         return ['#', r, g, b].join('');
34689     },
34690
34691     /**
34692      * Convert a color to hexadecimal format.
34693      *
34694      * @param {String|Array} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
34695      * Can also be an Array, in this case the function handles the first member.
34696      * @returns {String} The color in hexadecimal format.
34697      */
34698     toHex: function(color) {
34699         if (Ext.isArray(color)) {
34700             color = color[0];
34701         }
34702         if (!Ext.isString(color)) {
34703             return '';
34704         }
34705         if (color.substr(0, 1) === '#') {
34706             return color;
34707         }
34708         var digits = this.colorToHexRe.exec(color);
34709
34710         if (Ext.isArray(digits)) {
34711             var red = parseInt(digits[2], 10),
34712                 green = parseInt(digits[3], 10),
34713                 blue = parseInt(digits[4], 10),
34714                 rgb = blue | (green << 8) | (red << 16);
34715             return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
34716         }
34717         else {
34718             return '';
34719         }
34720     },
34721
34722     /**
34723      * Parse the string and create a new color.
34724      *
34725      * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
34726      *
34727      * If the string is not recognized, an undefined will be returned instead.
34728      *
34729      * @param {String} str Color in string.
34730      * @returns Ext.draw.Color
34731      */
34732     fromString: function(str) {
34733         var values, r, g, b,
34734             parse = parseInt;
34735
34736         if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
34737             values = str.match(this.hexRe);
34738             if (values) {
34739                 r = parse(values[1], 16) >> 0;
34740                 g = parse(values[2], 16) >> 0;
34741                 b = parse(values[3], 16) >> 0;
34742                 if (str.length == 4) {
34743                     r += (r * 16);
34744                     g += (g * 16);
34745                     b += (b * 16);
34746                 }
34747             }
34748         }
34749         else {
34750             values = str.match(this.rgbRe);
34751             if (values) {
34752                 r = values[1];
34753                 g = values[2];
34754                 b = values[3];
34755             }
34756         }
34757
34758         return (typeof r == 'undefined') ? undefined : Ext.create('Ext.draw.Color', r, g, b);
34759     },
34760
34761     /**
34762      * Returns the gray value (0 to 255) of the color.
34763      *
34764      * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
34765      *
34766      * @returns {Number}
34767      */
34768     getGrayscale: function() {
34769         // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
34770         return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
34771     },
34772
34773     /**
34774      * Create a new color based on the specified HSL values.
34775      *
34776      * @param {Number} h Hue component (0..359)
34777      * @param {Number} s Saturation component (0..1)
34778      * @param {Number} l Lightness component (0..1)
34779      * @returns Ext.draw.Color
34780      */
34781     fromHSL: function(h, s, l) {
34782         var C, X, m, i, rgb = [],
34783             abs = Math.abs,
34784             floor = Math.floor;
34785
34786         if (s == 0 || h == null) {
34787             // achromatic
34788             rgb = [l, l, l];
34789         }
34790         else {
34791             // http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
34792             // C is the chroma
34793             // X is the second largest component
34794             // m is the lightness adjustment
34795             h /= 60;
34796             C = s * (1 - abs(2 * l - 1));
34797             X = C * (1 - abs(h - 2 * floor(h / 2) - 1));
34798             m = l - C / 2;
34799             switch (floor(h)) {
34800                 case 0:
34801                     rgb = [C, X, 0];
34802                     break;
34803                 case 1:
34804                     rgb = [X, C, 0];
34805                     break;
34806                 case 2:
34807                     rgb = [0, C, X];
34808                     break;
34809                 case 3:
34810                     rgb = [0, X, C];
34811                     break;
34812                 case 4:
34813                     rgb = [X, 0, C];
34814                     break;
34815                 case 5:
34816                     rgb = [C, 0, X];
34817                     break;
34818             }
34819             rgb = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
34820         }
34821         return Ext.create('Ext.draw.Color', rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
34822     }
34823 }, function() {
34824     var prototype = this.prototype;
34825
34826     //These functions are both static and instance. TODO: find a more elegant way of copying them
34827     this.addStatics({
34828         fromHSL: function() {
34829             return prototype.fromHSL.apply(prototype, arguments);
34830         },
34831         fromString: function() {
34832             return prototype.fromString.apply(prototype, arguments);
34833         },
34834         toHex: function() {
34835             return prototype.toHex.apply(prototype, arguments);
34836         }
34837     });
34838 });
34839
34840 /**
34841  * @class Ext.dd.StatusProxy
34842  * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair.  This is the
34843  * default drag proxy used by all Ext.dd components.
34844  * @constructor
34845  * @param {Object} config
34846  */
34847 Ext.define('Ext.dd.StatusProxy', {
34848     animRepair: false,
34849
34850     constructor: function(config){
34851         Ext.apply(this, config);
34852         this.id = this.id || Ext.id();
34853         this.proxy = Ext.createWidget('component', {
34854             floating: true,
34855             id: this.id,
34856             html: '<div class="' + Ext.baseCSSPrefix + 'dd-drop-icon"></div>' +
34857                   '<div class="' + Ext.baseCSSPrefix + 'dd-drag-ghost"></div>',
34858             cls: Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed,
34859             shadow: !config || config.shadow !== false,
34860             renderTo: document.body
34861         });
34862
34863         this.el = this.proxy.el;
34864         this.el.show();
34865         this.el.setVisibilityMode(Ext.core.Element.VISIBILITY);
34866         this.el.hide();
34867
34868         this.ghost = Ext.get(this.el.dom.childNodes[1]);
34869         this.dropStatus = this.dropNotAllowed;
34870     },
34871     /**
34872      * @cfg {String} dropAllowed
34873      * The CSS class to apply to the status element when drop is allowed (defaults to "x-dd-drop-ok").
34874      */
34875     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
34876     /**
34877      * @cfg {String} dropNotAllowed
34878      * The CSS class to apply to the status element when drop is not allowed (defaults to "x-dd-drop-nodrop").
34879      */
34880     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
34881
34882     /**
34883      * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
34884      * over the current target element.
34885      * @param {String} cssClass The css class for the new drop status indicator image
34886      */
34887     setStatus : function(cssClass){
34888         cssClass = cssClass || this.dropNotAllowed;
34889         if(this.dropStatus != cssClass){
34890             this.el.replaceCls(this.dropStatus, cssClass);
34891             this.dropStatus = cssClass;
34892         }
34893     },
34894
34895     /**
34896      * Resets the status indicator to the default dropNotAllowed value
34897      * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
34898      */
34899     reset : function(clearGhost){
34900         this.el.dom.className = Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed;
34901         this.dropStatus = this.dropNotAllowed;
34902         if(clearGhost){
34903             this.ghost.update("");
34904         }
34905     },
34906
34907     /**
34908      * Updates the contents of the ghost element
34909      * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
34910      * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
34911      */
34912     update : function(html){
34913         if(typeof html == "string"){
34914             this.ghost.update(html);
34915         }else{
34916             this.ghost.update("");
34917             html.style.margin = "0";
34918             this.ghost.dom.appendChild(html);
34919         }
34920         var el = this.ghost.dom.firstChild; 
34921         if(el){
34922             Ext.fly(el).setStyle('float', 'none');
34923         }
34924     },
34925
34926     /**
34927      * Returns the underlying proxy {@link Ext.Layer}
34928      * @return {Ext.Layer} el
34929     */
34930     getEl : function(){
34931         return this.el;
34932     },
34933
34934     /**
34935      * Returns the ghost element
34936      * @return {Ext.core.Element} el
34937      */
34938     getGhost : function(){
34939         return this.ghost;
34940     },
34941
34942     /**
34943      * Hides the proxy
34944      * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
34945      */
34946     hide : function(clear) {
34947         this.proxy.hide();
34948         if (clear) {
34949             this.reset(true);
34950         }
34951     },
34952
34953     /**
34954      * Stops the repair animation if it's currently running
34955      */
34956     stop : function(){
34957         if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
34958             this.anim.stop();
34959         }
34960     },
34961
34962     /**
34963      * Displays this proxy
34964      */
34965     show : function() {
34966         this.proxy.show();
34967         this.proxy.toFront();
34968     },
34969
34970     /**
34971      * Force the Layer to sync its shadow and shim positions to the element
34972      */
34973     sync : function(){
34974         this.proxy.el.sync();
34975     },
34976
34977     /**
34978      * Causes the proxy to return to its position of origin via an animation.  Should be called after an
34979      * invalid drop operation by the item being dragged.
34980      * @param {Array} xy The XY position of the element ([x, y])
34981      * @param {Function} callback The function to call after the repair is complete.
34982      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
34983      */
34984     repair : function(xy, callback, scope){
34985         this.callback = callback;
34986         this.scope = scope;
34987         if (xy && this.animRepair !== false) {
34988             this.el.addCls(Ext.baseCSSPrefix + 'dd-drag-repair');
34989             this.el.hideUnders(true);
34990             this.anim = this.el.animate({
34991                 duration: this.repairDuration || 500,
34992                 easing: 'ease-out',
34993                 to: {
34994                     x: xy[0],
34995                     y: xy[1]
34996                 },
34997                 stopAnimation: true,
34998                 callback: this.afterRepair,
34999                 scope: this
35000             });
35001         } else {
35002             this.afterRepair();
35003         }
35004     },
35005
35006     // private
35007     afterRepair : function(){
35008         this.hide(true);
35009         if(typeof this.callback == "function"){
35010             this.callback.call(this.scope || this);
35011         }
35012         this.callback = null;
35013         this.scope = null;
35014     },
35015
35016     destroy: function(){
35017         Ext.destroy(this.ghost, this.proxy, this.el);
35018     }
35019 });
35020 /**
35021  * @class Ext.panel.Proxy
35022  * @extends Object
35023  * A custom drag proxy implementation specific to {@link Ext.panel.Panel}s. This class
35024  * is primarily used internally for the Panel's drag drop implementation, and
35025  * should never need to be created directly.
35026  * @constructor
35027  * @param panel The {@link Ext.panel.Panel} to proxy for
35028  * @param config Configuration options
35029  */
35030 Ext.define('Ext.panel.Proxy', {
35031     
35032     alternateClassName: 'Ext.dd.PanelProxy',
35033     
35034     constructor: function(panel, config){
35035         /**
35036          * @property panel
35037          * @type Ext.panel.Panel
35038          */
35039         this.panel = panel;
35040         this.id = this.panel.id +'-ddproxy';
35041         Ext.apply(this, config);
35042     },
35043
35044     /**
35045      * @cfg {Boolean} insertProxy True to insert a placeholder proxy element
35046      * while dragging the panel, false to drag with no proxy (defaults to true).
35047      * Most Panels are not absolute positioned and therefore we need to reserve
35048      * this space.
35049      */
35050     insertProxy: true,
35051
35052     // private overrides
35053     setStatus: Ext.emptyFn,
35054     reset: Ext.emptyFn,
35055     update: Ext.emptyFn,
35056     stop: Ext.emptyFn,
35057     sync: Ext.emptyFn,
35058
35059     /**
35060      * Gets the proxy's element
35061      * @return {Element} The proxy's element
35062      */
35063     getEl: function(){
35064         return this.ghost.el;
35065     },
35066
35067     /**
35068      * Gets the proxy's ghost Panel
35069      * @return {Panel} The proxy's ghost Panel
35070      */
35071     getGhost: function(){
35072         return this.ghost;
35073     },
35074
35075     /**
35076      * Gets the proxy element. This is the element that represents where the
35077      * Panel was before we started the drag operation.
35078      * @return {Element} The proxy's element
35079      */
35080     getProxy: function(){
35081         return this.proxy;
35082     },
35083
35084     /**
35085      * Hides the proxy
35086      */
35087     hide : function(){
35088         if (this.ghost) {
35089             if (this.proxy) {
35090                 this.proxy.remove();
35091                 delete this.proxy;
35092             }
35093
35094             // Unghost the Panel, do not move the Panel to where the ghost was
35095             this.panel.unghost(null, false);
35096             delete this.ghost;
35097         }
35098     },
35099
35100     /**
35101      * Shows the proxy
35102      */
35103     show: function(){
35104         if (!this.ghost) {
35105             var panelSize = this.panel.getSize();
35106             this.panel.el.setVisibilityMode(Ext.core.Element.DISPLAY);
35107             this.ghost = this.panel.ghost();
35108             if (this.insertProxy) {
35109                 // bc Panels aren't absolute positioned we need to take up the space
35110                 // of where the panel previously was
35111                 this.proxy = this.panel.el.insertSibling({cls: Ext.baseCSSPrefix + 'panel-dd-spacer'});
35112                 this.proxy.setSize(panelSize);
35113             }
35114         }
35115     },
35116
35117     // private
35118     repair: function(xy, callback, scope) {
35119         this.hide();
35120         if (typeof callback == "function") {
35121             callback.call(scope || this);
35122         }
35123     },
35124
35125     /**
35126      * Moves the proxy to a different position in the DOM.  This is typically
35127      * called while dragging the Panel to keep the proxy sync'd to the Panel's
35128      * location.
35129      * @param {HTMLElement} parentNode The proxy's parent DOM node
35130      * @param {HTMLElement} before (optional) The sibling node before which the
35131      * proxy should be inserted (defaults to the parent's last child if not
35132      * specified)
35133      */
35134     moveProxy : function(parentNode, before){
35135         if (this.proxy) {
35136             parentNode.insertBefore(this.proxy.dom, before);
35137         }
35138     }
35139 });
35140 /**
35141  * @class Ext.layout.component.AbstractDock
35142  * @extends Ext.layout.component.Component
35143  * @private
35144  * This ComponentLayout handles docking for Panels. It takes care of panels that are
35145  * part of a ContainerLayout that sets this Panel's size and Panels that are part of
35146  * an AutoContainerLayout in which this panel get his height based of the CSS or
35147  * or its content.
35148  */
35149
35150 Ext.define('Ext.layout.component.AbstractDock', {
35151
35152     /* Begin Definitions */
35153
35154     extend: 'Ext.layout.component.Component',
35155
35156     /* End Definitions */
35157
35158     type: 'dock',
35159
35160     /**
35161      * @private
35162      * @property autoSizing
35163      * @type boolean
35164      * This flag is set to indicate this layout may have an autoHeight/autoWidth.
35165      */
35166     autoSizing: true,
35167
35168     beforeLayout: function() {
35169         var returnValue = this.callParent(arguments);
35170         if (returnValue !== false && (!this.initializedBorders || this.childrenChanged) && (!this.owner.border || this.owner.manageBodyBorders)) {
35171             this.handleItemBorders();
35172             this.initializedBorders = true;
35173         }
35174         return returnValue;
35175     },
35176     
35177     handleItemBorders: function() {
35178         var owner = this.owner,
35179             body = owner.body,
35180             docked = this.getLayoutItems(),
35181             borders = {
35182                 top: [],
35183                 right: [],
35184                 bottom: [],
35185                 left: []
35186             },
35187             oldBorders = this.borders,
35188             opposites = {
35189                 top: 'bottom',
35190                 right: 'left',
35191                 bottom: 'top',
35192                 left: 'right'
35193             },
35194             i, ln, item, dock, side;
35195
35196         for (i = 0, ln = docked.length; i < ln; i++) {
35197             item = docked[i];
35198             dock = item.dock;
35199             
35200             if (item.ignoreBorderManagement) {
35201                 continue;
35202             }
35203             
35204             if (!borders[dock].satisfied) {
35205                 borders[dock].push(item);
35206                 borders[dock].satisfied = true;
35207             }
35208             
35209             if (!borders.top.satisfied && opposites[dock] !== 'top') {
35210                 borders.top.push(item);
35211             }
35212             if (!borders.right.satisfied && opposites[dock] !== 'right') {
35213                 borders.right.push(item);
35214             }            
35215             if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
35216                 borders.bottom.push(item);
35217             }            
35218             if (!borders.left.satisfied && opposites[dock] !== 'left') {
35219                 borders.left.push(item);
35220             }
35221         }
35222
35223         if (oldBorders) {
35224             for (side in oldBorders) {
35225                 if (oldBorders.hasOwnProperty(side)) {
35226                     ln = oldBorders[side].length;
35227                     if (!owner.manageBodyBorders) {
35228                         for (i = 0; i < ln; i++) {
35229                             oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
35230                         }
35231                         if (!oldBorders[side].satisfied && !owner.bodyBorder) {
35232                             body.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
35233                         }                    
35234                     }
35235                     else if (oldBorders[side].satisfied) {
35236                         body.setStyle('border-' + side + '-width', '');
35237                     }
35238                 }
35239             }
35240         }
35241                 
35242         for (side in borders) {
35243             if (borders.hasOwnProperty(side)) {
35244                 ln = borders[side].length;
35245                 if (!owner.manageBodyBorders) {
35246                     for (i = 0; i < ln; i++) {
35247                         borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
35248                     }
35249                     if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
35250                         body.addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
35251                     }                    
35252                 }
35253                 else if (borders[side].satisfied) {
35254                     body.setStyle('border-' + side + '-width', '1px');
35255                 }
35256             }
35257         }
35258         
35259         this.borders = borders;
35260     },
35261     
35262     /**
35263      * @protected
35264      * @param {Ext.Component} owner The Panel that owns this DockLayout
35265      * @param {Ext.core.Element} target The target in which we are going to render the docked items
35266      * @param {Array} args The arguments passed to the ComponentLayout.layout method
35267      */
35268     onLayout: function(width, height) {
35269         var me = this,
35270             owner = me.owner,
35271             body = owner.body,
35272             layout = owner.layout,
35273             target = me.getTarget(),
35274             autoWidth = false,
35275             autoHeight = false,
35276             padding, border, frameSize;
35277
35278         // We start of by resetting all the layouts info
35279         var info = me.info = {
35280             boxes: [],
35281             size: {
35282                 width: width,
35283                 height: height
35284             },
35285             bodyBox: {}
35286         };
35287
35288         Ext.applyIf(info, me.getTargetInfo());
35289
35290         // We need to bind to the ownerCt whenever we do not have a user set height or width.
35291         if (owner && owner.ownerCt && owner.ownerCt.layout && owner.ownerCt.layout.isLayout) {
35292             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
35293                 owner.ownerCt.layout.bindToOwnerCtComponent = true;
35294             }
35295             else {
35296                 owner.ownerCt.layout.bindToOwnerCtComponent = false;
35297             }
35298         }
35299
35300         // Determine if we have an autoHeight or autoWidth.
35301         if (height === undefined || height === null || width === undefined || width === null) {
35302             padding = info.padding;
35303             border = info.border;
35304             frameSize = me.frameSize;
35305
35306             // Auto-everything, clear out any style height/width and read from css
35307             if ((height === undefined || height === null) && (width === undefined || width === null)) {
35308                 autoHeight = true;
35309                 autoWidth = true;
35310                 me.setTargetSize(null);
35311                 me.setBodyBox({width: null, height: null});
35312             }
35313             // Auto-height
35314             else if (height === undefined || height === null) {
35315                 autoHeight = true;
35316                 // Clear any sizing that we already set in a previous layout
35317                 me.setTargetSize(width);
35318                 me.setBodyBox({width: width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: null});
35319             // Auto-width
35320             }
35321             else {
35322                 autoWidth = true;
35323                 // Clear any sizing that we already set in a previous layout
35324                 me.setTargetSize(null, height);
35325                 me.setBodyBox({width: null, height: height - padding.top - padding.bottom - border.top - border.bottom - frameSize.top - frameSize.bottom});
35326             }
35327
35328             // Run the container
35329             if (layout && layout.isLayout) {
35330                 // Auto-Sized so have the container layout notify the component layout.
35331                 layout.bindToOwnerCtComponent = true;
35332                 layout.layout();
35333
35334                 // If this is an autosized container layout, then we must compensate for a
35335                 // body that is being autosized.  We do not want to adjust the body's size
35336                 // to accommodate the dock items, but rather we will want to adjust the
35337                 // target's size.
35338                 //
35339                 // This is necessary because, particularly in a Box layout, all child items
35340                 // are set with absolute dimensions that are not flexible to the size of its
35341                 // innerCt/target.  So once they are laid out, they are sized for good. By
35342                 // shrinking the body box to accommodate dock items, we're merely cutting off
35343                 // parts of the body.  Not good.  Instead, the target's size should expand
35344                 // to fit the dock items in.  This is valid because the target container is
35345                 // suppose to be autosized to fit everything accordingly.
35346                 info.autoSizedCtLayout = layout.autoSize === true;
35347             }
35348
35349             // The dockItems method will add all the top and bottom docked items height
35350             // to the info.panelSize height. That's why we have to call setSize after
35351             // we dock all the items to actually set the panel's width and height.
35352             // We have to do this because the panel body and docked items will be position
35353             // absolute which doesn't stretch the panel.
35354             me.dockItems(autoWidth, autoHeight);
35355             me.setTargetSize(info.size.width, info.size.height);
35356         }
35357         else {
35358             me.setTargetSize(width, height);
35359             me.dockItems();
35360         }
35361         me.callParent(arguments);
35362     },
35363
35364     /**
35365      * @protected
35366      * This method will first update all the information about the docked items,
35367      * body dimensions and position, the panel's total size. It will then
35368      * set all these values on the docked items and panel body.
35369      * @param {Array} items Array containing all the docked items
35370      * @param {Boolean} autoBoxes Set this to true if the Panel is part of an
35371      * AutoContainerLayout
35372      */
35373     dockItems : function(autoWidth, autoHeight) {
35374         this.calculateDockBoxes(autoWidth, autoHeight);
35375
35376         // Both calculateAutoBoxes and calculateSizedBoxes are changing the
35377         // information about the body, panel size, and boxes for docked items
35378         // inside a property called info.
35379         var info = this.info,
35380             boxes = info.boxes,
35381             ln = boxes.length,
35382             dock, i;
35383
35384         // We are going to loop over all the boxes that were calculated
35385         // and set the position of each item the box belongs to.
35386         for (i = 0; i < ln; i++) {
35387             dock = boxes[i];
35388             dock.item.setPosition(dock.x, dock.y);
35389             if ((autoWidth || autoHeight) && dock.layout && dock.layout.isLayout) {
35390                 // Auto-Sized so have the container layout notify the component layout.
35391                 dock.layout.bindToOwnerCtComponent = true;
35392             }
35393         }
35394
35395         // Don't adjust body width/height if the target is using an auto container layout.
35396         // But, we do want to adjust the body size if the container layout is auto sized.
35397         if (!info.autoSizedCtLayout) {
35398             if (autoWidth) {
35399                 info.bodyBox.width = null;
35400             }
35401             if (autoHeight) {
35402                 info.bodyBox.height = null;
35403             }
35404         }
35405
35406         // If the bodyBox has been adjusted because of the docked items
35407         // we will update the dimensions and position of the panel's body.
35408         this.setBodyBox(info.bodyBox);
35409     },
35410
35411     /**
35412      * @protected
35413      * This method will set up some initial information about the panel size and bodybox
35414      * and then loop over all the items you pass it to take care of stretching, aligning,
35415      * dock position and all calculations involved with adjusting the body box.
35416      * @param {Array} items Array containing all the docked items we have to layout
35417      */
35418     calculateDockBoxes : function(autoWidth, autoHeight) {
35419         // We want to use the Panel's el width, and the Panel's body height as the initial
35420         // size we are going to use in calculateDockBoxes. We also want to account for
35421         // the border of the panel.
35422         var me = this,
35423             target = me.getTarget(),
35424             items = me.getLayoutItems(),
35425             owner = me.owner,
35426             bodyEl = owner.body,
35427             info = me.info,
35428             size = info.size,
35429             ln = items.length,
35430             padding = info.padding,
35431             border = info.border,
35432             frameSize = me.frameSize,
35433             item, i, box, rect;
35434
35435         // If this Panel is inside an AutoContainerLayout, we will base all the calculations
35436         // around the height of the body and the width of the panel.
35437         if (autoHeight) {
35438             size.height = bodyEl.getHeight() + padding.top + border.top + padding.bottom + border.bottom + frameSize.top + frameSize.bottom;
35439         }
35440         else {
35441             size.height = target.getHeight();
35442         }
35443         if (autoWidth) {
35444             size.width = bodyEl.getWidth() + padding.left + border.left + padding.right + border.right + frameSize.left + frameSize.right;
35445         }
35446         else {
35447             size.width = target.getWidth();
35448         }
35449
35450         info.bodyBox = {
35451             x: padding.left + frameSize.left,
35452             y: padding.top + frameSize.top,
35453             width: size.width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right,
35454             height: size.height - border.top - padding.top - border.bottom - padding.bottom - frameSize.top - frameSize.bottom
35455         };
35456
35457         // Loop over all the docked items
35458         for (i = 0; i < ln; i++) {
35459             item = items[i];
35460             // The initBox method will take care of stretching and alignment
35461             // In some cases it will also layout the dock items to be able to
35462             // get a width or height measurement
35463             box = me.initBox(item);
35464
35465             if (autoHeight === true) {
35466                 box = me.adjustAutoBox(box, i);
35467             }
35468             else {
35469                 box = me.adjustSizedBox(box, i);
35470             }
35471
35472             // Save our box. This allows us to loop over all docked items and do all
35473             // calculations first. Then in one loop we will actually size and position
35474             // all the docked items that have changed.
35475             info.boxes.push(box);
35476         }
35477     },
35478
35479     /**
35480      * @protected
35481      * This method will adjust the position of the docked item and adjust the body box
35482      * accordingly.
35483      * @param {Object} box The box containing information about the width and height
35484      * of this docked item
35485      * @param {Number} index The index position of this docked item
35486      * @return {Object} The adjusted box
35487      */
35488     adjustSizedBox : function(box, index) {
35489         var bodyBox = this.info.bodyBox,
35490             frameSize = this.frameSize,
35491             info = this.info,
35492             padding = info.padding,
35493             pos = box.type,
35494             border = info.border;
35495
35496         switch (pos) {
35497             case 'top':
35498                 box.y = bodyBox.y;
35499                 break;
35500
35501             case 'left':
35502                 box.x = bodyBox.x;
35503                 break;
35504
35505             case 'bottom':
35506                 box.y = (bodyBox.y + bodyBox.height) - box.height;
35507                 break;
35508
35509             case 'right':
35510                 box.x = (bodyBox.x + bodyBox.width) - box.width;
35511                 break;
35512         }
35513
35514         if (box.ignoreFrame) {
35515             if (pos == 'bottom') {
35516                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
35517             }
35518             else {
35519                 box.y -= (frameSize.top + padding.top + border.top);
35520             }
35521             if (pos == 'right') {
35522                 box.x += (frameSize.right + padding.right + border.right);
35523             }
35524             else {
35525                 box.x -= (frameSize.left + padding.left + border.left);
35526             }
35527         }
35528
35529         // If this is not an overlaying docked item, we have to adjust the body box
35530         if (!box.overlay) {
35531             switch (pos) {
35532                 case 'top':
35533                     bodyBox.y += box.height;
35534                     bodyBox.height -= box.height;
35535                     break;
35536
35537                 case 'left':
35538                     bodyBox.x += box.width;
35539                     bodyBox.width -= box.width;
35540                     break;
35541
35542                 case 'bottom':
35543                     bodyBox.height -= box.height;
35544                     break;
35545
35546                 case 'right':
35547                     bodyBox.width -= box.width;
35548                     break;
35549             }
35550         }
35551         return box;
35552     },
35553
35554     /**
35555      * @protected
35556      * This method will adjust the position of the docked item inside an AutoContainerLayout
35557      * and adjust the body box accordingly.
35558      * @param {Object} box The box containing information about the width and height
35559      * of this docked item
35560      * @param {Number} index The index position of this docked item
35561      * @return {Object} The adjusted box
35562      */
35563     adjustAutoBox : function (box, index) {
35564         var info = this.info,
35565             bodyBox = info.bodyBox,
35566             size = info.size,
35567             boxes = info.boxes,
35568             boxesLn = boxes.length,
35569             pos = box.type,
35570             frameSize = this.frameSize,
35571             padding = info.padding,
35572             border = info.border,
35573             autoSizedCtLayout = info.autoSizedCtLayout,
35574             ln = (boxesLn < index) ? boxesLn : index,
35575             i, adjustBox;
35576
35577         if (pos == 'top' || pos == 'bottom') {
35578             // This can affect the previously set left and right and bottom docked items
35579             for (i = 0; i < ln; i++) {
35580                 adjustBox = boxes[i];
35581                 if (adjustBox.stretched && adjustBox.type == 'left' || adjustBox.type == 'right') {
35582                     adjustBox.height += box.height;
35583                 }
35584                 else if (adjustBox.type == 'bottom') {
35585                     adjustBox.y += box.height;
35586                 }
35587             }
35588         }
35589
35590         switch (pos) {
35591             case 'top':
35592                 box.y = bodyBox.y;
35593                 if (!box.overlay) {
35594                     bodyBox.y += box.height;
35595                 }
35596                 size.height += box.height;
35597                 break;
35598
35599             case 'bottom':
35600                 box.y = (bodyBox.y + bodyBox.height);
35601                 size.height += box.height;
35602                 break;
35603
35604             case 'left':
35605                 box.x = bodyBox.x;
35606                 if (!box.overlay) {
35607                     bodyBox.x += box.width;
35608                     if (autoSizedCtLayout) {
35609                         size.width += box.width;
35610                     } else {
35611                         bodyBox.width -= box.width;
35612                     }
35613                 }
35614                 break;
35615
35616             case 'right':
35617                 if (!box.overlay) {
35618                     if (autoSizedCtLayout) {
35619                         size.width += box.width;
35620                     } else {
35621                         bodyBox.width -= box.width;
35622                     }
35623                 }
35624                 box.x = (bodyBox.x + bodyBox.width);
35625                 break;
35626         }
35627
35628         if (box.ignoreFrame) {
35629             if (pos == 'bottom') {
35630                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
35631             }
35632             else {
35633                 box.y -= (frameSize.top + padding.top + border.top);
35634             }
35635             if (pos == 'right') {
35636                 box.x += (frameSize.right + padding.right + border.right);
35637             }
35638             else {
35639                 box.x -= (frameSize.left + padding.left + border.left);
35640             }
35641         }
35642         return box;
35643     },
35644
35645     /**
35646      * @protected
35647      * This method will create a box object, with a reference to the item, the type of dock
35648      * (top, left, bottom, right). It will also take care of stretching and aligning of the
35649      * docked items.
35650      * @param {Ext.Component} item The docked item we want to initialize the box for
35651      * @return {Object} The initial box containing width and height and other useful information
35652      */
35653     initBox : function(item) {
35654         var me = this,
35655             bodyBox = me.info.bodyBox,
35656             horizontal = (item.dock == 'top' || item.dock == 'bottom'),
35657             owner = me.owner,
35658             frameSize = me.frameSize,
35659             info = me.info,
35660             padding = info.padding,
35661             border = info.border,
35662             box = {
35663                 item: item,
35664                 overlay: item.overlay,
35665                 type: item.dock,
35666                 offsets: Ext.core.Element.parseBox(item.offsets || {}),
35667                 ignoreFrame: item.ignoreParentFrame
35668             };
35669         // First we are going to take care of stretch and align properties for all four dock scenarios.
35670         if (item.stretch !== false) {
35671             box.stretched = true;
35672             if (horizontal) {
35673                 box.x = bodyBox.x + box.offsets.left;
35674                 box.width = bodyBox.width - (box.offsets.left + box.offsets.right);
35675                 if (box.ignoreFrame) {
35676                     box.width += (frameSize.left + frameSize.right + border.left + border.right + padding.left + padding.right);
35677                 }
35678                 item.setCalculatedSize(box.width - item.el.getMargin('lr'), undefined, owner);
35679             }
35680             else {
35681                 box.y = bodyBox.y + box.offsets.top;
35682                 box.height = bodyBox.height - (box.offsets.bottom + box.offsets.top);
35683                 if (box.ignoreFrame) {
35684                     box.height += (frameSize.top + frameSize.bottom + border.top + border.bottom + padding.top + padding.bottom);
35685                 }
35686                 item.setCalculatedSize(undefined, box.height - item.el.getMargin('tb'), owner);
35687
35688                 // At this point IE will report the left/right-docked toolbar as having a width equal to the
35689                 // container's full width. Forcing a repaint kicks it into shape so it reports the correct width.
35690                 if (!Ext.supports.ComputedStyle) {
35691                     item.el.repaint();
35692                 }
35693             }
35694         }
35695         else {
35696             item.doComponentLayout();
35697             box.width = item.getWidth() - (box.offsets.left + box.offsets.right);
35698             box.height = item.getHeight() - (box.offsets.bottom + box.offsets.top);
35699             box.y += box.offsets.top;
35700             if (horizontal) {
35701                 box.x = (item.align == 'right') ? bodyBox.width - box.width : bodyBox.x;
35702                 box.x += box.offsets.left;
35703             }
35704         }
35705
35706         // If we haven't calculated the width or height of the docked item yet
35707         // do so, since we need this for our upcoming calculations
35708         if (box.width == undefined) {
35709             box.width = item.getWidth() + item.el.getMargin('lr');
35710         }
35711         if (box.height == undefined) {
35712             box.height = item.getHeight() + item.el.getMargin('tb');
35713         }
35714
35715         return box;
35716     },
35717
35718     /**
35719      * @protected
35720      * Returns an array containing all the <b>visible</b> docked items inside this layout's owner Panel
35721      * @return {Array} An array containing all the <b>visible</b> docked items of the Panel
35722      */
35723     getLayoutItems : function() {
35724         var it = this.owner.getDockedItems(),
35725             ln = it.length,
35726             i = 0,
35727             result = [];
35728         for (; i < ln; i++) {
35729             if (it[i].isVisible(true)) {
35730                 result.push(it[i]);
35731             }
35732         }
35733         return result;
35734     },
35735
35736     /**
35737      * @protected
35738      * Render the top and left docked items before any existing DOM nodes in our render target,
35739      * and then render the right and bottom docked items after. This is important, for such things
35740      * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
35741      * Our collection of docked items will already be ordered via Panel.getDockedItems().
35742      */
35743     renderItems: function(items, target) {
35744         var cns = target.dom.childNodes,
35745             cnsLn = cns.length,
35746             ln = items.length,
35747             domLn = 0,
35748             i, j, cn, item;
35749
35750         // Calculate the number of DOM nodes in our target that are not our docked items
35751         for (i = 0; i < cnsLn; i++) {
35752             cn = Ext.get(cns[i]);
35753             for (j = 0; j < ln; j++) {
35754                 item = items[j];
35755                 if (item.rendered && (cn.id == item.el.id || cn.down('#' + item.el.id))) {
35756                     break;
35757                 }
35758             }
35759
35760             if (j === ln) {
35761                 domLn++;
35762             }
35763         }
35764
35765         // Now we go through our docked items and render/move them
35766         for (i = 0, j = 0; i < ln; i++, j++) {
35767             item = items[i];
35768
35769             // If we're now at the right/bottom docked item, we jump ahead in our
35770             // DOM position, just past the existing DOM nodes.
35771             //
35772             // TODO: This is affected if users provide custom weight values to their
35773             // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
35774             // sort operation here, for now, in the name of performance. getDockedItems()
35775             // needs the sort operation not just for this layout-time rendering, but
35776             // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
35777             if (i === j && (item.dock === 'right' || item.dock === 'bottom')) {
35778                 j += domLn;
35779             }
35780
35781             // Same logic as Layout.renderItems()
35782             if (item && !item.rendered) {
35783                 this.renderItem(item, target, j);
35784             }
35785             else if (!this.isValidParent(item, target, j)) {
35786                 this.moveItem(item, target, j);
35787             }
35788         }
35789     },
35790
35791     /**
35792      * @protected
35793      * This function will be called by the dockItems method. Since the body is positioned absolute,
35794      * we need to give it dimensions and a position so that it is in the middle surrounded by
35795      * docked items
35796      * @param {Object} box An object containing new x, y, width and height values for the
35797      * Panel's body
35798      */
35799     setBodyBox : function(box) {
35800         var me = this,
35801             owner = me.owner,
35802             body = owner.body,
35803             info = me.info,
35804             bodyMargin = info.bodyMargin,
35805             padding = info.padding,
35806             border = info.border,
35807             frameSize = me.frameSize;
35808         
35809         // Panel collapse effectively hides the Panel's body, so this is a no-op.
35810         if (owner.collapsed) {
35811             return;
35812         }
35813         
35814         if (Ext.isNumber(box.width)) {
35815             box.width -= bodyMargin.left + bodyMargin.right;
35816         }
35817         
35818         if (Ext.isNumber(box.height)) {
35819             box.height -= bodyMargin.top + bodyMargin.bottom;
35820         }
35821         
35822         me.setElementSize(body, box.width, box.height);
35823         if (Ext.isNumber(box.x)) {
35824             body.setLeft(box.x - padding.left - frameSize.left);
35825         }
35826         if (Ext.isNumber(box.y)) {
35827             body.setTop(box.y - padding.top - frameSize.top);
35828         }
35829     },
35830
35831     /**
35832      * @protected
35833      * We are overriding the Ext.layout.Layout configureItem method to also add a class that
35834      * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
35835      * An example of a class added to a dock: right item is x-docked-right
35836      * @param {Ext.Component} item The item we are configuring
35837      */
35838     configureItem : function(item, pos) {
35839         this.callParent(arguments);
35840         
35841         item.addCls(Ext.baseCSSPrefix + 'docked');
35842         item.addClsWithUI('docked-' + item.dock);
35843     },
35844
35845     afterRemove : function(item) {
35846         this.callParent(arguments);
35847         if (this.itemCls) {
35848             item.el.removeCls(this.itemCls + '-' + item.dock);
35849         }
35850         var dom = item.el.dom;
35851
35852         if (!item.destroying && dom) {
35853             dom.parentNode.removeChild(dom);
35854         }
35855         this.childrenChanged = true;
35856     }
35857 });
35858 /**
35859  * @class Ext.app.EventBus
35860  * @private
35861  *
35862  * Class documentation for the MVC classes will be present before 4.0 final, in the mean time please refer to the MVC
35863  * guide
35864  */
35865 Ext.define('Ext.app.EventBus', {
35866     requires: [
35867         'Ext.util.Event'
35868     ],
35869     mixins: {
35870         observable: 'Ext.util.Observable'
35871     },
35872     
35873     constructor: function() {
35874         this.mixins.observable.constructor.call(this);
35875         
35876         this.bus = {};
35877         
35878         var me = this;
35879         Ext.override(Ext.Component, {
35880             fireEvent: function(ev) {
35881                 if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
35882                     return me.dispatch.call(me, ev, this, arguments);
35883                 }
35884                 return false;
35885             }
35886         });
35887     },
35888
35889     dispatch: function(ev, target, args) {
35890         var bus = this.bus,
35891             selectors = bus[ev],
35892             selector, controllers, id, events, event, i, ln;
35893         
35894         if (selectors) {
35895             // Loop over all the selectors that are bound to this event
35896             for (selector in selectors) {
35897                 // Check if the target matches the selector
35898                 if (target.is(selector)) {
35899                     // Loop over all the controllers that are bound to this selector   
35900                     controllers = selectors[selector];
35901                     for (id in controllers) {
35902                         // Loop over all the events that are bound to this selector on this controller
35903                         events = controllers[id];
35904                         for (i = 0, ln = events.length; i < ln; i++) {
35905                             event = events[i];
35906                             // Fire the event!
35907                             return event.fire.apply(event, Array.prototype.slice.call(args, 1));
35908                         }
35909                     }
35910                 }
35911             }
35912         }
35913     },
35914     
35915     control: function(selectors, listeners, controller) {
35916         var bus = this.bus,
35917             selector, fn;
35918         
35919         if (Ext.isString(selectors)) {
35920             selector = selectors;
35921             selectors = {};
35922             selectors[selector] = listeners;
35923             this.control(selectors, null, controller);
35924             return;
35925         }
35926     
35927         Ext.Object.each(selectors, function(selector, listeners) {
35928             Ext.Object.each(listeners, function(ev, listener) {
35929                 var options = {},   
35930                     scope = controller,
35931                     event = Ext.create('Ext.util.Event', controller, ev);
35932                 
35933                 // Normalize the listener                
35934                 if (Ext.isObject(listener)) {
35935                     options = listener;
35936                     listener = options.fn;
35937                     scope = options.scope || controller;
35938                     delete options.fn;
35939                     delete options.scope;
35940                 }
35941                 
35942                 event.addListener(listener, scope, options);
35943
35944                 // Create the bus tree if it is not there yet
35945                 bus[ev] = bus[ev] || {};
35946                 bus[ev][selector] = bus[ev][selector] || {};
35947                 bus[ev][selector][controller.id] = bus[ev][selector][controller.id] || [];            
35948                 
35949                 // Push our listener in our bus
35950                 bus[ev][selector][controller.id].push(event);
35951             });
35952         });
35953     }
35954 });
35955 /**
35956  * @class Ext.data.Types
35957  * <p>This is s static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
35958  * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
35959  * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
35960  * of this class.</p>
35961  * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
35962  * each type definition must contain three properties:</p>
35963  * <div class="mdetail-params"><ul>
35964  * <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
35965  * to be stored in the Field. The function is passed the collowing parameters:
35966  * <div class="mdetail-params"><ul>
35967  * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
35968  * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
35969  * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
35970  * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
35971  * ({@link Ext.data.reader.Json JsonReader}), or an XML element.</div></li>
35972  * </ul></div></div></li>
35973  * <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>
35974  * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
35975  * </ul></div>
35976  * <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
35977  * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
35978  *<pre><code>
35979 // Add a new Field data type which stores a VELatLong object in the Record.
35980 Ext.data.Types.VELATLONG = {
35981     convert: function(v, data) {
35982         return new VELatLong(data.lat, data.long);
35983     },
35984     sortType: function(v) {
35985         return v.Latitude;  // When sorting, order by latitude
35986     },
35987     type: 'VELatLong'
35988 };
35989 </code></pre>
35990  * <p>Then, when declaring a Model, use <pre><code>
35991 var types = Ext.data.Types; // allow shorthand type access
35992 Ext.define('Unit',
35993     extend: 'Ext.data.Model', 
35994     fields: [
35995         { name: 'unitName', mapping: 'UnitName' },
35996         { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
35997         { name: 'latitude', mapping: 'lat', type: types.FLOAT },
35998         { name: 'latitude', mapping: 'lat', type: types.FLOAT },
35999         { name: 'position', type: types.VELATLONG }
36000     ]
36001 });
36002 </code></pre>
36003  * @singleton
36004  */
36005 Ext.define('Ext.data.Types', {
36006     singleton: true,
36007     requires: ['Ext.data.SortTypes']
36008 }, function() {
36009     var st = Ext.data.SortTypes;
36010     
36011     Ext.apply(Ext.data.Types, {
36012         /**
36013          * @type Regexp
36014          * @property stripRe
36015          * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
36016          * This should be overridden for localization.
36017          */
36018         stripRe: /[\$,%]/g,
36019         
36020         /**
36021          * @type Object.
36022          * @property AUTO
36023          * This data type means that no conversion is applied to the raw data before it is placed into a Record.
36024          */
36025         AUTO: {
36026             convert: function(v) {
36027                 return v;
36028             },
36029             sortType: st.none,
36030             type: 'auto'
36031         },
36032
36033         /**
36034          * @type Object.
36035          * @property STRING
36036          * This data type means that the raw data is converted into a String before it is placed into a Record.
36037          */
36038         STRING: {
36039             convert: function(v) {
36040                 var defaultValue = this.useNull ? null : '';
36041                 return (v === undefined || v === null) ? defaultValue : String(v);
36042             },
36043             sortType: st.asUCString,
36044             type: 'string'
36045         },
36046
36047         /**
36048          * @type Object.
36049          * @property INT
36050          * This data type means that the raw data is converted into an integer before it is placed into a Record.
36051          * <p>The synonym <code>INTEGER</code> is equivalent.</p>
36052          */
36053         INT: {
36054             convert: function(v) {
36055                 return v !== undefined && v !== null && v !== '' ?
36056                     parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
36057             },
36058             sortType: st.none,
36059             type: 'int'
36060         },
36061         
36062         /**
36063          * @type Object.
36064          * @property FLOAT
36065          * This data type means that the raw data is converted into a number before it is placed into a Record.
36066          * <p>The synonym <code>NUMBER</code> is equivalent.</p>
36067          */
36068         FLOAT: {
36069             convert: function(v) {
36070                 return v !== undefined && v !== null && v !== '' ?
36071                     parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
36072             },
36073             sortType: st.none,
36074             type: 'float'
36075         },
36076         
36077         /**
36078          * @type Object.
36079          * @property BOOL
36080          * <p>This data type means that the raw data is converted into a boolean before it is placed into
36081          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
36082          * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
36083          */
36084         BOOL: {
36085             convert: function(v) {
36086                 if (this.useNull && v === undefined || v === null || v === '') {
36087                     return null;
36088                 }
36089                 return v === true || v === 'true' || v == 1;
36090             },
36091             sortType: st.none,
36092             type: 'bool'
36093         },
36094         
36095         /**
36096          * @type Object.
36097          * @property DATE
36098          * This data type means that the raw data is converted into a Date before it is placed into a Record.
36099          * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
36100          * being applied.
36101          */
36102         DATE: {
36103             convert: function(v) {
36104                 var df = this.dateFormat;
36105                 if (!v) {
36106                     return null;
36107                 }
36108                 if (Ext.isDate(v)) {
36109                     return v;
36110                 }
36111                 if (df) {
36112                     if (df == 'timestamp') {
36113                         return new Date(v*1000);
36114                     }
36115                     if (df == 'time') {
36116                         return new Date(parseInt(v, 10));
36117                     }
36118                     return Ext.Date.parse(v, df);
36119                 }
36120                 
36121                 var parsed = Date.parse(v);
36122                 return parsed ? new Date(parsed) : null;
36123             },
36124             sortType: st.asDate,
36125             type: 'date'
36126         }
36127     });
36128     
36129     Ext.apply(Ext.data.Types, {
36130         /**
36131          * @type Object.
36132          * @property BOOLEAN
36133          * <p>This data type means that the raw data is converted into a boolean before it is placed into
36134          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
36135          * <p>The synonym <code>BOOL</code> is equivalent.</p>
36136          */
36137         BOOLEAN: this.BOOL,
36138         
36139         /**
36140          * @type Object.
36141          * @property INTEGER
36142          * This data type means that the raw data is converted into an integer before it is placed into a Record.
36143          * <p>The synonym <code>INT</code> is equivalent.</p>
36144          */
36145         INTEGER: this.INT,
36146         
36147         /**
36148          * @type Object.
36149          * @property NUMBER
36150          * This data type means that the raw data is converted into a number before it is placed into a Record.
36151          * <p>The synonym <code>FLOAT</code> is equivalent.</p>
36152          */
36153         NUMBER: this.FLOAT    
36154     });
36155 });
36156
36157 /**
36158  * @author Ed Spencer
36159  * @class Ext.data.Field
36160  * @extends Object
36161  * 
36162  * <p>Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class 
36163  * that extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a 
36164  * {@link Ext.data.Model Model}. For example, we might set up a model like this:</p>
36165  * 
36166 <pre><code>
36167 Ext.define('User', {
36168     extend: 'Ext.data.Model',
36169     fields: [
36170         'name', 'email',
36171         {name: 'age', type: 'int'},
36172         {name: 'gender', type: 'string', defaultValue: 'Unknown'}
36173     ]
36174 });
36175 </code></pre>
36176  * 
36177  * <p>Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a
36178  * couple of different formats here; if we only pass in the string name of the field (as with name and email), the
36179  * field is set up with the 'auto' type. It's as if we'd done this instead:</p>
36180  * 
36181 <pre><code>
36182 Ext.define('User', {
36183     extend: 'Ext.data.Model',
36184     fields: [
36185         {name: 'name', type: 'auto'},
36186         {name: 'email', type: 'auto'},
36187         {name: 'age', type: 'int'},
36188         {name: 'gender', type: 'string', defaultValue: 'Unknown'}
36189     ]
36190 });
36191 </code></pre>
36192  * 
36193  * <p><u>Types and conversion</u></p>
36194  * 
36195  * <p>The {@link #type} is important - it's used to automatically convert data passed to the field into the correct
36196  * format. In our example above, the name and email fields used the 'auto' type and will just accept anything that is
36197  * passed into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.</p>
36198  * 
36199  * <p>Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can
36200  * do this using a {@link #convert} function. Here, we're going to create a new field based on another:</p>
36201  * 
36202 <code><pre>
36203 Ext.define('User', {
36204     extend: 'Ext.data.Model',
36205     fields: [
36206         'name', 'email',
36207         {name: 'age', type: 'int'},
36208         {name: 'gender', type: 'string', defaultValue: 'Unknown'},
36209
36210         {
36211             name: 'firstName',
36212             convert: function(value, record) {
36213                 var fullName  = record.get('name'),
36214                     splits    = fullName.split(" "),
36215                     firstName = splits[0];
36216
36217                 return firstName;
36218             }
36219         }
36220     ]
36221 });
36222 </code></pre>
36223  * 
36224  * <p>Now when we create a new User, the firstName is populated automatically based on the name:</p>
36225  * 
36226 <code><pre>
36227 var ed = Ext.ModelManager.create({name: 'Ed Spencer'}, 'User');
36228
36229 console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
36230 </code></pre>
36231  * 
36232  * <p>In fact, if we log out all of the data inside ed, we'll see this:</p>
36233  * 
36234 <code><pre>
36235 console.log(ed.data);
36236
36237 //outputs this:
36238 {
36239     age: 0,
36240     email: "",
36241     firstName: "Ed",
36242     gender: "Unknown",
36243     name: "Ed Spencer"
36244 }
36245 </code></pre>
36246  * 
36247  * <p>The age field has been given a default of zero because we made it an int type. As an auto field, email has
36248  * defaulted to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown'
36249  * so we see that now. Let's correct that and satisfy ourselves that the types work as we expect:</p>
36250  * 
36251 <code><pre>
36252 ed.set('gender', 'Male');
36253 ed.get('gender'); //returns 'Male'
36254
36255 ed.set('age', 25.4);
36256 ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
36257 </code></pre>
36258  * 
36259  */
36260 Ext.define('Ext.data.Field', {
36261     requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
36262     alias: 'data.field',
36263     
36264     constructor : function(config) {
36265         if (Ext.isString(config)) {
36266             config = {name: config};
36267         }
36268         Ext.apply(this, config);
36269         
36270         var types = Ext.data.Types,
36271             st = this.sortType,
36272             t;
36273
36274         if (this.type) {
36275             if (Ext.isString(this.type)) {
36276                 this.type = types[this.type.toUpperCase()] || types.AUTO;
36277             }
36278         } else {
36279             this.type = types.AUTO;
36280         }
36281
36282         // named sortTypes are supported, here we look them up
36283         if (Ext.isString(st)) {
36284             this.sortType = Ext.data.SortTypes[st];
36285         } else if(Ext.isEmpty(st)) {
36286             this.sortType = this.type.sortType;
36287         }
36288
36289         if (!this.convert) {
36290             this.convert = this.type.convert;
36291         }
36292     },
36293     
36294     /**
36295      * @cfg {String} name
36296      * The name by which the field is referenced within the Model. This is referenced by, for example,
36297      * the <code>dataIndex</code> property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
36298      * <p>Note: In the simplest case, if no properties other than <code>name</code> are required, a field
36299      * definition may consist of just a String for the field name.</p>
36300      */
36301     
36302     /**
36303      * @cfg {Mixed} type
36304      * (Optional) The data type for automatic conversion from received data to the <i>stored</i> value if <code>{@link Ext.data.Field#convert convert}</code>
36305      * has not been specified. This may be specified as a string value. Possible values are
36306      * <div class="mdetail-params"><ul>
36307      * <li>auto (Default, implies no conversion)</li>
36308      * <li>string</li>
36309      * <li>int</li>
36310      * <li>float</li>
36311      * <li>boolean</li>
36312      * <li>date</li></ul></div>
36313      * <p>This may also be specified by referencing a member of the {@link Ext.data.Types} class.</p>
36314      * <p>Developers may create their own application-specific data types by defining new members of the
36315      * {@link Ext.data.Types} class.</p>
36316      */
36317     
36318     /**
36319      * @cfg {Function} convert
36320      * (Optional) A function which converts the value provided by the Reader into an object that will be stored
36321      * in the Model. It is passed the following parameters:<div class="mdetail-params"><ul>
36322      * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
36323      * the configured <code>{@link Ext.data.Field#defaultValue defaultValue}</code>.</div></li>
36324      * <li><b>rec</b> : Ext.data.Model<div class="sub-desc">The data object containing the Model as read so far by the 
36325      * Reader. Note that the Model may not be fully populated at this point as the fields are read in the order that 
36326      * they are defined in your {@link #fields} array.</div></li>
36327      * </ul></div>
36328      * <pre><code>
36329 // example of convert function
36330 function fullName(v, record){
36331     return record.name.last + ', ' + record.name.first;
36332 }
36333
36334 function location(v, record){
36335     return !record.city ? '' : (record.city + ', ' + record.state);
36336 }
36337
36338 Ext.define('Dude', {
36339     extend: 'Ext.data.Model',
36340     fields: [
36341         {name: 'fullname',  convert: fullName},
36342         {name: 'firstname', mapping: 'name.first'},
36343         {name: 'lastname',  mapping: 'name.last'},
36344         {name: 'city', defaultValue: 'homeless'},
36345         'state',
36346         {name: 'location',  convert: location}
36347     ]
36348 });
36349
36350 // create the data store
36351 var store = new Ext.data.Store({
36352     reader: {
36353         type: 'json',
36354         model: 'Dude',
36355         idProperty: 'key',
36356         root: 'daRoot',
36357         totalProperty: 'total'
36358     }
36359 });
36360
36361 var myData = [
36362     { key: 1,
36363       name: { first: 'Fat',    last:  'Albert' }
36364       // notice no city, state provided in data object
36365     },
36366     { key: 2,
36367       name: { first: 'Barney', last:  'Rubble' },
36368       city: 'Bedrock', state: 'Stoneridge'
36369     },
36370     { key: 3,
36371       name: { first: 'Cliff',  last:  'Claven' },
36372       city: 'Boston',  state: 'MA'
36373     }
36374 ];
36375      * </code></pre>
36376      */
36377     /**
36378      * @cfg {String} dateFormat
36379      * <p>(Optional) Used when converting received data into a Date when the {@link #type} is specified as <code>"date"</code>.</p>
36380      * <p>A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the
36381      * value provided by the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a
36382      * javascript millisecond timestamp. See {@link Date}</p>
36383      */
36384     dateFormat: null,
36385     
36386     /**
36387      * @cfg {Boolean} useNull
36388      * <p>(Optional) Use when converting received data into a Number type (either int or float). If the value cannot be parsed,
36389      * null will be used if useNull is true, otherwise the value will be 0. Defaults to <tt>false</tt>
36390      */
36391     useNull: false,
36392     
36393     /**
36394      * @cfg {Mixed} defaultValue
36395      * (Optional) The default value used <b>when a Model is being created by a {@link Ext.data.reader.Reader Reader}</b>
36396      * when the item referenced by the <code>{@link Ext.data.Field#mapping mapping}</code> does not exist in the data
36397      * object (i.e. undefined). (defaults to "")
36398      */
36399     defaultValue: "",
36400     /**
36401      * @cfg {String/Number} mapping
36402      * <p>(Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation
36403      * that is creating the {@link Ext.data.Model Model} to extract the Field value from the data object.
36404      * If the path expression is the same as the field name, the mapping may be omitted.</p>
36405      * <p>The form of the mapping expression depends on the Reader being used.</p>
36406      * <div class="mdetail-params"><ul>
36407      * <li>{@link Ext.data.reader.Json}<div class="sub-desc">The mapping is a string containing the javascript
36408      * expression to reference the data from an element of the data item's {@link Ext.data.reader.Json#root root} Array. Defaults to the field name.</div></li>
36409      * <li>{@link Ext.data.reader.Xml}<div class="sub-desc">The mapping is an {@link Ext.DomQuery} path to the data
36410      * item relative to the DOM element that represents the {@link Ext.data.reader.Xml#record record}. Defaults to the field name.</div></li>
36411      * <li>{@link Ext.data.reader.Array}<div class="sub-desc">The mapping is a number indicating the Array index
36412      * of the field's value. Defaults to the field specification's Array position.</div></li>
36413      * </ul></div>
36414      * <p>If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
36415      * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
36416      * return the desired data.</p>
36417      */
36418     mapping: null,
36419     /**
36420      * @cfg {Function} sortType
36421      * (Optional) A function which converts a Field's value to a comparable value in order to ensure
36422      * correct sort ordering. Predefined functions are provided in {@link Ext.data.SortTypes}. A custom
36423      * sort example:<pre><code>
36424 // current sort     after sort we want
36425 // +-+------+          +-+------+
36426 // |1|First |          |1|First |
36427 // |2|Last  |          |3|Second|
36428 // |3|Second|          |2|Last  |
36429 // +-+------+          +-+------+
36430
36431 sortType: function(value) {
36432    switch (value.toLowerCase()) // native toLowerCase():
36433    {
36434       case 'first': return 1;
36435       case 'second': return 2;
36436       default: return 3;
36437    }
36438 }
36439      * </code></pre>
36440      */
36441     sortType : null,
36442     /**
36443      * @cfg {String} sortDir
36444      * (Optional) Initial direction to sort (<code>"ASC"</code> or  <code>"DESC"</code>).  Defaults to
36445      * <code>"ASC"</code>.
36446      */
36447     sortDir : "ASC",
36448     /**
36449      * @cfg {Boolean} allowBlank
36450      * @private
36451      * (Optional) Used for validating a {@link Ext.data.Model model}, defaults to <code>true</code>.
36452      * An empty value here will cause {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid}
36453      * to evaluate to <code>false</code>.
36454      */
36455     allowBlank : true,
36456     
36457     /**
36458      * @cfg {Boolean} persist
36459      * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This 
36460      * will also exclude the field from being written using a {@link Ext.data.writer.Writer}. This option
36461      * is useful when model fields are used to keep state on the client but do not need to be persisted
36462      * to the server. Defaults to <tt>true</tt>.
36463      */
36464     persist: true
36465 });
36466
36467 /**
36468  * @author Ed Spencer
36469  * @class Ext.data.reader.Reader
36470  * @extends Object
36471  * 
36472  * <p>Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link Ext.data.Store Store}
36473  * - usually in response to an AJAX request. This is normally handled transparently by passing some configuration to either the 
36474  * {@link Ext.data.Model Model} or the {@link Ext.data.Store Store} in question - see their documentation for further details.</p>
36475  * 
36476  * <p><u>Loading Nested Data</u></p>
36477  * 
36478  * <p>Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.Association associations}
36479  * configured on each Model. Below is an example demonstrating the flexibility of these associations in a fictional CRM system which
36480  * manages a User, their Orders, OrderItems and Products. First we'll define the models:
36481  * 
36482 <pre><code>
36483 Ext.define("User", {
36484     extend: 'Ext.data.Model',
36485     fields: [
36486         'id', 'name'
36487     ],
36488
36489     hasMany: {model: 'Order', name: 'orders'},
36490
36491     proxy: {
36492         type: 'rest',
36493         url : 'users.json',
36494         reader: {
36495             type: 'json',
36496             root: 'users'
36497         }
36498     }
36499 });
36500
36501 Ext.define("Order", {
36502     extend: 'Ext.data.Model',
36503     fields: [
36504         'id', 'total'
36505     ],
36506
36507     hasMany  : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
36508     belongsTo: 'User'
36509 });
36510
36511 Ext.define("OrderItem", {
36512     extend: 'Ext.data.Model',
36513     fields: [
36514         'id', 'price', 'quantity', 'order_id', 'product_id'
36515     ],
36516
36517     belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
36518 });
36519
36520 Ext.define("Product", {
36521     extend: 'Ext.data.Model',
36522     fields: [
36523         'id', 'name'
36524     ],
36525
36526     hasMany: 'OrderItem'
36527 });
36528 </code></pre>
36529  * 
36530  * <p>This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems. Finally,
36531  * each OrderItem has a single Product. This allows us to consume data like this:</p>
36532  * 
36533 <pre><code>
36534 {
36535     "users": [
36536         {
36537             "id": 123,
36538             "name": "Ed",
36539             "orders": [
36540                 {
36541                     "id": 50,
36542                     "total": 100,
36543                     "order_items": [
36544                         {
36545                             "id"      : 20,
36546                             "price"   : 40,
36547                             "quantity": 2,
36548                             "product" : {
36549                                 "id": 1000,
36550                                 "name": "MacBook Pro"
36551                             }
36552                         },
36553                         {
36554                             "id"      : 21,
36555                             "price"   : 20,
36556                             "quantity": 3,
36557                             "product" : {
36558                                 "id": 1001,
36559                                 "name": "iPhone"
36560                             }
36561                         }
36562                     ]
36563                 }
36564             ]
36565         }
36566     ]
36567 }
36568 </code></pre>
36569  * 
36570  * <p>The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the Orders
36571  * for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case), and finally
36572  * the Product associated with each OrderItem. Now we can read the data and use it as follows:
36573  * 
36574 <pre><code>
36575 var store = new Ext.data.Store({
36576     model: "User"
36577 });
36578
36579 store.load({
36580     callback: function() {
36581         //the user that was loaded
36582         var user = store.first();
36583
36584         console.log("Orders for " + user.get('name') + ":")
36585
36586         //iterate over the Orders for each User
36587         user.orders().each(function(order) {
36588             console.log("Order ID: " + order.getId() + ", which contains items:");
36589
36590             //iterate over the OrderItems for each Order
36591             order.orderItems().each(function(orderItem) {
36592                 //we know that the Product data is already loaded, so we can use the synchronous getProduct
36593                 //usually, we would use the asynchronous version (see {@link Ext.data.BelongsToAssociation})
36594                 var product = orderItem.getProduct();
36595
36596                 console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
36597             });
36598         });
36599     }
36600 });
36601 </code></pre>
36602  * 
36603  * <p>Running the code above results in the following:</p>
36604  * 
36605 <pre><code>
36606 Orders for Ed:
36607 Order ID: 50, which contains items:
36608 2 orders of MacBook Pro
36609 3 orders of iPhone
36610 </code></pre>
36611  * 
36612  * @constructor
36613  * @param {Object} config Optional config object
36614  */
36615 Ext.define('Ext.data.reader.Reader', {
36616     requires: ['Ext.data.ResultSet'],
36617     alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
36618     
36619     /**
36620      * @cfg {String} idProperty Name of the property within a row object
36621      * that contains a record identifier value.  Defaults to <tt>The id of the model</tt>.
36622      * If an idProperty is explicitly specified it will override that of the one specified
36623      * on the model
36624      */
36625
36626     /**
36627      * @cfg {String} totalProperty Name of the property from which to
36628      * retrieve the total number of records in the dataset. This is only needed
36629      * if the whole dataset is not passed in one go, but is being paged from
36630      * the remote server.  Defaults to <tt>total</tt>.
36631      */
36632     totalProperty: 'total',
36633
36634     /**
36635      * @cfg {String} successProperty Name of the property from which to
36636      * retrieve the success attribute. Defaults to <tt>success</tt>.  See
36637      * {@link Ext.data.proxy.Proxy}.{@link Ext.data.proxy.Proxy#exception exception}
36638      * for additional information.
36639      */
36640     successProperty: 'success',
36641
36642     /**
36643      * @cfg {String} root <b>Required</b>.  The name of the property
36644      * which contains the Array of row objects.  Defaults to <tt>undefined</tt>.
36645      * An exception will be thrown if the root property is undefined. The data
36646      * packet value for this property should be an empty array to clear the data
36647      * or show no data.
36648      */
36649     root: '',
36650     
36651     /**
36652      * @cfg {String} messageProperty The name of the property which contains a response message.
36653      * This property is optional.
36654      */
36655     
36656     /**
36657      * @cfg {Boolean} implicitIncludes True to automatically parse models nested within other models in a response
36658      * object. See the Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
36659      */
36660     implicitIncludes: true,
36661     
36662     isReader: true,
36663     
36664     constructor: function(config) {
36665         var me = this;
36666         
36667         Ext.apply(me, config || {});
36668         me.fieldCount = 0;
36669         me.model = Ext.ModelManager.getModel(config.model);
36670         if (me.model) {
36671             me.buildExtractors();
36672         }
36673     },
36674
36675     /**
36676      * Sets a new model for the reader.
36677      * @private
36678      * @param {Object} model The model to set.
36679      * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
36680      */
36681     setModel: function(model, setOnProxy) {
36682         var me = this;
36683         
36684         me.model = Ext.ModelManager.getModel(model);
36685         me.buildExtractors(true);
36686         
36687         if (setOnProxy && me.proxy) {
36688             me.proxy.setModel(me.model, true);
36689         }
36690     },
36691
36692     /**
36693      * Reads the given response object. This method normalizes the different types of response object that may be passed
36694      * to it, before handing off the reading of records to the {@link #readRecords} function.
36695      * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
36696      * @return {Ext.data.ResultSet} The parsed ResultSet object
36697      */
36698     read: function(response) {
36699         var data = response;
36700         
36701         if (response && response.responseText) {
36702             data = this.getResponseData(response);
36703         }
36704         
36705         if (data) {
36706             return this.readRecords(data);
36707         } else {
36708             return this.nullResultSet;
36709         }
36710     },
36711
36712     /**
36713      * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call
36714      * this function before running its own logic and returning the Ext.data.ResultSet instance. For most
36715      * Readers additional processing should not be needed.
36716      * @param {Mixed} data The raw data object
36717      * @return {Ext.data.ResultSet} A ResultSet object
36718      */
36719     readRecords: function(data) {
36720         var me  = this;
36721         
36722         /*
36723          * We check here whether the number of fields has changed since the last read.
36724          * This works around an issue when a Model is used for both a Tree and another
36725          * source, because the tree decorates the model with extra fields and it causes
36726          * issues because the readers aren't notified.
36727          */
36728         if (me.fieldCount !== me.getFields().length) {
36729             me.buildExtractors(true);
36730         }
36731         
36732         /**
36733          * The raw data object that was last passed to readRecords. Stored for further processing if needed
36734          * @property rawData
36735          * @type Mixed
36736          */
36737         me.rawData = data;
36738
36739         data = me.getData(data);
36740
36741         // If we pass an array as the data, we dont use getRoot on the data.
36742         // Instead the root equals to the data.
36743         var root    = Ext.isArray(data) ? data : me.getRoot(data),
36744             success = true,
36745             recordCount = 0,
36746             total, value, records, message;
36747             
36748         if (root) {
36749             total = root.length;
36750         }
36751
36752         if (me.totalProperty) {
36753             value = parseInt(me.getTotal(data), 10);
36754             if (!isNaN(value)) {
36755                 total = value;
36756             }
36757         }
36758
36759         if (me.successProperty) {
36760             value = me.getSuccess(data);
36761             if (value === false || value === 'false') {
36762                 success = false;
36763             }
36764         }
36765         
36766         if (me.messageProperty) {
36767             message = me.getMessage(data);
36768         }
36769         
36770         if (root) {
36771             records = me.extractData(root);
36772             recordCount = records.length;
36773         } else {
36774             recordCount = 0;
36775             records = [];
36776         }
36777
36778         return Ext.create('Ext.data.ResultSet', {
36779             total  : total || recordCount,
36780             count  : recordCount,
36781             records: records,
36782             success: success,
36783             message: message
36784         });
36785     },
36786
36787     /**
36788      * Returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
36789      * @param {Object[]/Object} data-root from server response
36790      * @private
36791      */
36792     extractData : function(root) {
36793         var me = this,
36794             values  = [],
36795             records = [],
36796             Model   = me.model,
36797             i       = 0,
36798             length  = root.length,
36799             idProp  = me.getIdProperty(),
36800             node, id, record;
36801             
36802         if (!root.length && Ext.isObject(root)) {
36803             root = [root];
36804             length = 1;
36805         }
36806
36807         for (; i < length; i++) {
36808             node   = root[i];
36809             values = me.extractValues(node);
36810             id     = me.getId(node);
36811
36812             
36813             record = new Model(values, id, node);
36814             records.push(record);
36815                 
36816             if (me.implicitIncludes) {
36817                 me.readAssociated(record, node);
36818             }
36819         }
36820
36821         return records;
36822     },
36823     
36824     /**
36825      * @private
36826      * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
36827      * on the record provided.
36828      * @param {Ext.data.Model} record The record to load associations for
36829      * @param {Mixed} data The data object
36830      * @return {String} Return value description
36831      */
36832     readAssociated: function(record, data) {
36833         var associations = record.associations.items,
36834             i            = 0,
36835             length       = associations.length,
36836             association, associationData, proxy, reader;
36837         
36838         for (; i < length; i++) {
36839             association     = associations[i];
36840             associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
36841             
36842             if (associationData) {
36843                 reader = association.getReader();
36844                 if (!reader) {
36845                     proxy = association.associatedModel.proxy;
36846                     // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
36847                     if (proxy) {
36848                         reader = proxy.getReader();
36849                     } else {
36850                         reader = new this.constructor({
36851                             model: association.associatedName
36852                         });
36853                     }
36854                 }
36855                 association.read(record, reader, associationData);
36856             }  
36857         }
36858     },
36859     
36860     /**
36861      * @private
36862      * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
36863      * record, this should return the relevant part of that data for the given association name. This is only really
36864      * needed to support the XML Reader, which has to do a query to get the associated data object
36865      * @param {Mixed} data The raw data object
36866      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
36867      * @return {Mixed} The root
36868      */
36869     getAssociatedDataRoot: function(data, associationName) {
36870         return data[associationName];
36871     },
36872     
36873     getFields: function() {
36874         return this.model.prototype.fields.items;
36875     },
36876
36877     /**
36878      * @private
36879      * Given an object representing a single model instance's data, iterates over the model's fields and
36880      * builds an object with the value for each field.
36881      * @param {Object} data The data object to convert
36882      * @return {Object} Data object suitable for use with a model constructor
36883      */
36884     extractValues: function(data) {
36885         var fields = this.getFields(),
36886             i      = 0,
36887             length = fields.length,
36888             output = {},
36889             field, value;
36890
36891         for (; i < length; i++) {
36892             field = fields[i];
36893             value = this.extractorFunctions[i](data);
36894
36895             output[field.name] = value;
36896         }
36897
36898         return output;
36899     },
36900
36901     /**
36902      * @private
36903      * By default this function just returns what is passed to it. It can be overridden in a subclass
36904      * to return something else. See XmlReader for an example.
36905      * @param {Object} data The data object
36906      * @return {Object} The normalized data object
36907      */
36908     getData: function(data) {
36909         return data;
36910     },
36911
36912     /**
36913      * @private
36914      * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
36915      * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
36916      * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
36917      * @param {Mixed} data The data object
36918      * @return {Mixed} The same data object
36919      */
36920     getRoot: function(data) {
36921         return data;
36922     },
36923
36924     /**
36925      * Takes a raw response object (as passed to this.read) and returns the useful data segment of it. This must be implemented by each subclass
36926      * @param {Object} response The responce object
36927      * @return {Object} The useful data from the response
36928      */
36929     getResponseData: function(response) {
36930         Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");
36931     },
36932
36933     /**
36934      * @private
36935      * Reconfigures the meta data tied to this Reader
36936      */
36937     onMetaChange : function(meta) {
36938         var fields = meta.fields,
36939             newModel;
36940         
36941         Ext.apply(this, meta);
36942         
36943         if (fields) {
36944             newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
36945                 extend: 'Ext.data.Model',
36946                 fields: fields
36947             });
36948             this.setModel(newModel, true);
36949         } else {
36950             this.buildExtractors(true);
36951         }
36952     },
36953     
36954     /**
36955      * Get the idProperty to use for extracting data
36956      * @private
36957      * @return {String} The id property
36958      */
36959     getIdProperty: function(){
36960         var prop = this.idProperty;
36961         if (Ext.isEmpty(prop)) {
36962             prop = this.model.prototype.idProperty;
36963         }
36964         return prop;
36965     },
36966
36967     /**
36968      * @private
36969      * This builds optimized functions for retrieving record data and meta data from an object.
36970      * Subclasses may need to implement their own getRoot function.
36971      * @param {Boolean} force True to automatically remove existing extractor functions first (defaults to false)
36972      */
36973     buildExtractors: function(force) {
36974         var me          = this,
36975             idProp      = me.getIdProperty(),
36976             totalProp   = me.totalProperty,
36977             successProp = me.successProperty,
36978             messageProp = me.messageProperty,
36979             accessor;
36980             
36981         if (force === true) {
36982             delete me.extractorFunctions;
36983         }
36984         
36985         if (me.extractorFunctions) {
36986             return;
36987         }   
36988
36989         //build the extractors for all the meta data
36990         if (totalProp) {
36991             me.getTotal = me.createAccessor(totalProp);
36992         }
36993
36994         if (successProp) {
36995             me.getSuccess = me.createAccessor(successProp);
36996         }
36997
36998         if (messageProp) {
36999             me.getMessage = me.createAccessor(messageProp);
37000         }
37001
37002         if (idProp) {
37003             accessor = me.createAccessor(idProp);
37004
37005             me.getId = function(record) {
37006                 var id = accessor.call(me, record);
37007                 return (id === undefined || id === '') ? null : id;
37008             };
37009         } else {
37010             me.getId = function() {
37011                 return null;
37012             };
37013         }
37014         me.buildFieldExtractors();
37015     },
37016
37017     /**
37018      * @private
37019      */
37020     buildFieldExtractors: function() {
37021         //now build the extractors for all the fields
37022         var me = this,
37023             fields = me.getFields(),
37024             ln = fields.length,
37025             i  = 0,
37026             extractorFunctions = [],
37027             field, map;
37028
37029         for (; i < ln; i++) {
37030             field = fields[i];
37031             map   = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
37032
37033             extractorFunctions.push(me.createAccessor(map));
37034         }
37035         me.fieldCount = ln;
37036
37037         me.extractorFunctions = extractorFunctions;
37038     }
37039 }, function() {
37040     Ext.apply(this, {
37041         // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
37042         nullResultSet: Ext.create('Ext.data.ResultSet', {
37043             total  : 0,
37044             count  : 0,
37045             records: [],
37046             success: true
37047         })
37048     });
37049 });
37050 /**
37051  * @author Ed Spencer
37052  * @class Ext.data.reader.Json
37053  * @extends Ext.data.reader.Reader
37054  * 
37055  * <p>The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
37056  * happens as a result of loading a Store - for example we might create something like this:</p>
37057  * 
37058 <pre><code>
37059 Ext.define('User', {
37060     extend: 'Ext.data.Model',
37061     fields: ['id', 'name', 'email']
37062 });
37063
37064 var store = new Ext.data.Store({
37065     model: 'User',
37066     proxy: {
37067         type: 'ajax',
37068         url : 'users.json',
37069         reader: {
37070             type: 'json'
37071         }
37072     }
37073 });
37074 </code></pre>
37075  * 
37076  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
37077  * not already familiar with them.</p>
37078  * 
37079  * <p>We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s 
37080  * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
37081  * Store, so it is as if we passed this instead:
37082  * 
37083 <pre><code>
37084 reader: {
37085     type : 'json',
37086     model: 'User'
37087 }
37088 </code></pre>
37089  * 
37090  * <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>
37091  * 
37092 <pre><code>
37093 [
37094     {
37095         "id": 1,
37096         "name": "Ed Spencer",
37097         "email": "ed@sencha.com"
37098     },
37099     {
37100         "id": 2,
37101         "name": "Abe Elias",
37102         "email": "abe@sencha.com"
37103     }
37104 ]
37105 </code></pre>
37106  * 
37107  * <p><u>Reading other JSON formats</u></p>
37108  * 
37109  * <p>If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
37110  * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the 
37111  * {@link #root} configuration to parse data that comes back like this:</p>
37112  * 
37113 <pre><code>
37114 {
37115     "users": [
37116        {
37117            "id": 1,
37118            "name": "Ed Spencer",
37119            "email": "ed@sencha.com"
37120        },
37121        {
37122            "id": 2,
37123            "name": "Abe Elias",
37124            "email": "abe@sencha.com"
37125        }
37126     ]
37127 }
37128 </code></pre>
37129  * 
37130  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
37131  * 
37132 <pre><code>
37133 reader: {
37134     type: 'json',
37135     root: 'users'
37136 }
37137 </code></pre>
37138  * 
37139  * <p>Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
37140  * around each record inside a nested structure like this:</p>
37141  * 
37142 <pre><code>
37143 {
37144     "total": 122,
37145     "offset": 0,
37146     "users": [
37147         {
37148             "id": "ed-spencer-1",
37149             "value": 1,
37150             "user": {
37151                 "id": 1,
37152                 "name": "Ed Spencer",
37153                 "email": "ed@sencha.com"
37154             }
37155         }
37156     ]
37157 }
37158 </code></pre>
37159  * 
37160  * <p>In the case above the record data is nested an additional level inside the "users" array as each "user" item has
37161  * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the 
37162  * JSON above we need to specify the {@link #record} configuration like this:</p>
37163  * 
37164 <pre><code>
37165 reader: {
37166     type  : 'json',
37167     root  : 'users',
37168     record: 'user'
37169 }
37170 </code></pre>
37171  * 
37172  * <p><u>Response metadata</u></p>
37173  * 
37174  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records} 
37175  * and the {@link #successProperty success status of the response}. These are typically included in the JSON response
37176  * like this:</p>
37177  * 
37178 <pre><code>
37179 {
37180     "total": 100,
37181     "success": true,
37182     "users": [
37183         {
37184             "id": 1,
37185             "name": "Ed Spencer",
37186             "email": "ed@sencha.com"
37187         }
37188     ]
37189 }
37190 </code></pre>
37191  * 
37192  * <p>If these properties are present in the JSON response they can be parsed out by the JsonReader and used by the
37193  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration 
37194  * options:</p>
37195  * 
37196 <pre><code>
37197 reader: {
37198     type : 'json',
37199     root : 'users',
37200     totalProperty  : 'total',
37201     successProperty: 'success'
37202 }
37203 </code></pre>
37204  * 
37205  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
37206  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
37207  * returned.</p>
37208  */
37209 Ext.define('Ext.data.reader.Json', {
37210     extend: 'Ext.data.reader.Reader',
37211     alternateClassName: 'Ext.data.JsonReader',
37212     alias : 'reader.json',
37213     
37214     root: '',
37215     
37216     /**
37217      * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
37218      * See the JsonReader intro docs for more details. This is not often needed and defaults to undefined.
37219      */
37220     
37221     /**
37222      * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
37223      * reading values. Defalts to <tt>false</tt>.
37224      * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
37225      * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name 
37226      * "foo.bar.baz" direct from the root object.
37227      */
37228     useSimpleAccessors: false,
37229     
37230     /**
37231      * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
37232      * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
37233      * @param {Object} data The raw JSON data
37234      * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
37235      */
37236     readRecords: function(data) {
37237         //this has to be before the call to super because we use the meta data in the superclass readRecords
37238         if (data.metaData) {
37239             this.onMetaChange(data.metaData);
37240         }
37241
37242         /**
37243          * DEPRECATED - will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
37244          * @property jsonData
37245          * @type Mixed
37246          */
37247         this.jsonData = data;
37248         return this.callParent([data]);
37249     },
37250
37251     //inherit docs
37252     getResponseData: function(response) {
37253         try {
37254             var data = Ext.decode(response.responseText);
37255         }
37256         catch (ex) {
37257             Ext.Error.raise({
37258                 response: response,
37259                 json: response.responseText,
37260                 parseError: ex,
37261                 msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
37262             });
37263         }
37264         if (!data) {
37265             Ext.Error.raise('JSON object not found');
37266         }
37267
37268         return data;
37269     },
37270
37271     //inherit docs
37272     buildExtractors : function() {
37273         var me = this;
37274         
37275         me.callParent(arguments);
37276
37277         if (me.root) {
37278             me.getRoot = me.createAccessor(me.root);
37279         } else {
37280             me.getRoot = function(root) {
37281                 return root;
37282             };
37283         }
37284     },
37285     
37286     /**
37287      * @private
37288      * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
37289      * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
37290      * @param {Object} root The JSON root node
37291      * @return {Array} The records
37292      */
37293     extractData: function(root) {
37294         var recordName = this.record,
37295             data = [],
37296             length, i;
37297         
37298         if (recordName) {
37299             length = root.length;
37300             
37301             for (i = 0; i < length; i++) {
37302                 data[i] = root[i][recordName];
37303             }
37304         } else {
37305             data = root;
37306         }
37307         return this.callParent([data]);
37308     },
37309
37310     /**
37311      * @private
37312      * Returns an accessor function for the given property string. Gives support for properties such as the following:
37313      * 'someProperty'
37314      * 'some.property'
37315      * 'some["property"]'
37316      * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
37317      */
37318     createAccessor: function() {
37319         var re = /[\[\.]/;
37320         
37321         return function(expr) {
37322             if (Ext.isEmpty(expr)) {
37323                 return Ext.emptyFn;
37324             }
37325             if (Ext.isFunction(expr)) {
37326                 return expr;
37327             }
37328             if (this.useSimpleAccessors !== true) {
37329                 var i = String(expr).search(re);
37330                 if (i >= 0) {
37331                     return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
37332                 }
37333             }
37334             return function(obj) {
37335                 return obj[expr];
37336             };
37337         };
37338     }()
37339 });
37340 /**
37341  * @class Ext.data.writer.Json
37342  * @extends Ext.data.writer.Writer
37343
37344 This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
37345 The {@link #allowSingle} configuration can be set to false to force the records to always be
37346 encoded in an array, even if there is only a single record being sent.
37347
37348  * @markdown
37349  */
37350 Ext.define('Ext.data.writer.Json', {
37351     extend: 'Ext.data.writer.Writer',
37352     alternateClassName: 'Ext.data.JsonWriter',
37353     alias: 'writer.json',
37354     
37355     /**
37356      * @cfg {String} root The key under which the records in this Writer will be placed. Defaults to <tt>undefined</tt>.
37357      * Example generated request, using root: 'records':
37358 <pre><code>
37359 {'records': [{name: 'my record'}, {name: 'another record'}]}
37360 </code></pre>
37361      */
37362     root: undefined,
37363     
37364     /**
37365      * @cfg {Boolean} encode True to use Ext.encode() on the data before sending. Defaults to <tt>false</tt>.
37366      * The encode option should only be set to true when a {@link #root} is defined, because the values will be
37367      * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter
37368      * sent to the server.
37369      */
37370     encode: false,
37371     
37372     /**
37373      * @cfg {Boolean} allowSingle False to ensure that records are always wrapped in an array, even if there is only
37374      * one record being sent. When there is more than one record, they will always be encoded into an array.
37375      * Defaults to <tt>true</tt>. Example:
37376      * <pre><code>
37377 // with allowSingle: true
37378 "root": {
37379     "first": "Mark",
37380     "last": "Corrigan"
37381 }
37382
37383 // with allowSingle: false
37384 "root": [{
37385     "first": "Mark",
37386     "last": "Corrigan"
37387 }]
37388      * </code></pre>
37389      */
37390     allowSingle: true,
37391     
37392     //inherit docs
37393     writeRecords: function(request, data) {
37394         var root = this.root;
37395         
37396         if (this.allowSingle && data.length == 1) {
37397             // convert to single object format
37398             data = data[0];
37399         }
37400         
37401         if (this.encode) {
37402             if (root) {
37403                 // sending as a param, need to encode
37404                 request.params[root] = Ext.encode(data);
37405             } else {
37406                 Ext.Error.raise('Must specify a root when using encode');
37407             }
37408         } else {
37409             // send as jsonData
37410             request.jsonData = request.jsonData || {};
37411             if (root) {
37412                 request.jsonData[root] = data;
37413             } else {
37414                 request.jsonData = data;
37415             }
37416         }
37417         return request;
37418     }
37419 });
37420
37421 /**
37422  * @author Ed Spencer
37423  * @class Ext.data.proxy.Proxy
37424  * 
37425  * <p>Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model} data.
37426  * Usually developers will not need to create or interact with proxies directly.</p>
37427  * <p><u>Types of Proxy</u></p>
37428  * 
37429  * <p>There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}. The Client proxies
37430  * save their data locally and include the following subclasses:</p>
37431  * 
37432  * <ul style="list-style-type: disc; padding-left: 25px">
37433  * <li>{@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it</li>
37434  * <li>{@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage if the browsers supports it</li>
37435  * <li>{@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed</li>
37436  * </ul>
37437  * 
37438  * <p>The Server proxies save their data by sending requests to some remote server. These proxies include:</p>
37439  * 
37440  * <ul style="list-style-type: disc; padding-left: 25px">
37441  * <li>{@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain</li>
37442  * <li>{@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain</li>
37443  * <li>{@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct} to send requests</li>
37444  * </ul>
37445  * 
37446  * <p>Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four operations 
37447  * are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy} respectively. Each Proxy subclass 
37448  * implements these functions.</p>
37449  * 
37450  * <p>The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation encapsulates 
37451  * information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances that are to be modified, etc.
37452  * See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD method also accepts a callback function to be 
37453  * called asynchronously on completion.</p>
37454  * 
37455  * <p>Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch} method.</p>
37456  * 
37457  * @constructor
37458  * Creates the Proxy
37459  * @param {Object} config Optional config object
37460  */
37461 Ext.define('Ext.data.proxy.Proxy', {
37462     alias: 'proxy.proxy',
37463     alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
37464     requires: [
37465         'Ext.data.reader.Json',
37466         'Ext.data.writer.Json'
37467     ],
37468     uses: [
37469         'Ext.data.Batch', 
37470         'Ext.data.Operation', 
37471         'Ext.data.Model'
37472     ],
37473     mixins: {
37474         observable: 'Ext.util.Observable'
37475     },
37476     
37477     /**
37478      * @cfg {String} batchOrder
37479      * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this
37480      * to set a different order for the batched CRUD actions to be executed in. Defaults to 'create,update,destroy'
37481      */
37482     batchOrder: 'create,update,destroy',
37483     
37484     /**
37485      * @cfg {Boolean} batchActions True to batch actions of a particular type when synchronizing the store.
37486      * Defaults to <tt>true</tt>.
37487      */
37488     batchActions: true,
37489     
37490     /**
37491      * @cfg {String} defaultReaderType The default registered reader type. Defaults to 'json'
37492      * @private
37493      */
37494     defaultReaderType: 'json',
37495     
37496     /**
37497      * @cfg {String} defaultWriterType The default registered writer type. Defaults to 'json'
37498      * @private
37499      */
37500     defaultWriterType: 'json',
37501     
37502     /**
37503      * @cfg {String/Ext.data.Model} model The name of the Model to tie to this Proxy. Can be either the string name of
37504      * the Model, or a reference to the Model constructor. Required.
37505      */
37506     
37507     isProxy: true,
37508     
37509     constructor: function(config) {
37510         config = config || {};
37511         
37512         if (config.model === undefined) {
37513             delete config.model;
37514         }
37515
37516         this.mixins.observable.constructor.call(this, config);
37517         
37518         if (this.model !== undefined && !(this.model instanceof Ext.data.Model)) {
37519             this.setModel(this.model);
37520         }
37521     },
37522     
37523     /**
37524      * Sets the model associated with this proxy. This will only usually be called by a Store
37525      * @param {String|Ext.data.Model} model The new model. Can be either the model name string,
37526      * or a reference to the model's constructor
37527      * @param {Boolean} setOnStore Sets the new model on the associated Store, if one is present
37528      */
37529     setModel: function(model, setOnStore) {
37530         this.model = Ext.ModelManager.getModel(model);
37531         
37532         var reader = this.reader,
37533             writer = this.writer;
37534         
37535         this.setReader(reader);
37536         this.setWriter(writer);
37537         
37538         if (setOnStore && this.store) {
37539             this.store.setModel(this.model);
37540         }
37541     },
37542     
37543     /**
37544      * Returns the model attached to this Proxy
37545      * @return {Ext.data.Model} The model
37546      */
37547     getModel: function() {
37548         return this.model;
37549     },
37550     
37551     /**
37552      * Sets the Proxy's Reader by string, config object or Reader instance
37553      * @param {String|Object|Ext.data.reader.Reader} reader The new Reader, which can be either a type string, a configuration object
37554      * or an Ext.data.reader.Reader instance
37555      * @return {Ext.data.reader.Reader} The attached Reader object
37556      */
37557     setReader: function(reader) {
37558         var me = this;
37559         
37560         if (reader === undefined || typeof reader == 'string') {
37561             reader = {
37562                 type: reader
37563             };
37564         }
37565
37566         if (reader.isReader) {
37567             reader.setModel(me.model);
37568         } else {
37569             Ext.applyIf(reader, {
37570                 proxy: me,
37571                 model: me.model,
37572                 type : me.defaultReaderType
37573             });
37574
37575             reader = Ext.createByAlias('reader.' + reader.type, reader);
37576         }
37577         
37578         me.reader = reader;
37579         return me.reader;
37580     },
37581     
37582     /**
37583      * Returns the reader currently attached to this proxy instance
37584      * @return {Ext.data.reader.Reader} The Reader instance
37585      */
37586     getReader: function() {
37587         return this.reader;
37588     },
37589     
37590     /**
37591      * Sets the Proxy's Writer by string, config object or Writer instance
37592      * @param {String|Object|Ext.data.writer.Writer} writer The new Writer, which can be either a type string, a configuration object
37593      * or an Ext.data.writer.Writer instance
37594      * @return {Ext.data.writer.Writer} The attached Writer object
37595      */
37596     setWriter: function(writer) {
37597         if (writer === undefined || typeof writer == 'string') {
37598             writer = {
37599                 type: writer
37600             };
37601         }
37602
37603         if (!(writer instanceof Ext.data.writer.Writer)) {
37604             Ext.applyIf(writer, {
37605                 model: this.model,
37606                 type : this.defaultWriterType
37607             });
37608
37609             writer = Ext.createByAlias('writer.' + writer.type, writer);
37610         }
37611         
37612         this.writer = writer;
37613         
37614         return this.writer;
37615     },
37616     
37617     /**
37618      * Returns the writer currently attached to this proxy instance
37619      * @return {Ext.data.writer.Writer} The Writer instance
37620      */
37621     getWriter: function() {
37622         return this.writer;
37623     },
37624     
37625     /**
37626      * Performs the given create operation.
37627      * @param {Ext.data.Operation} operation The Operation to perform
37628      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
37629      * @param {Object} scope Scope to execute the callback function in
37630      * @method
37631      */
37632     create: Ext.emptyFn,
37633     
37634     /**
37635      * Performs the given read operation.
37636      * @param {Ext.data.Operation} operation The Operation to perform
37637      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
37638      * @param {Object} scope Scope to execute the callback function in
37639      * @method
37640      */
37641     read: Ext.emptyFn,
37642     
37643     /**
37644      * Performs the given update operation.
37645      * @param {Ext.data.Operation} operation The Operation to perform
37646      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
37647      * @param {Object} scope Scope to execute the callback function in
37648      * @method
37649      */
37650     update: Ext.emptyFn,
37651     
37652     /**
37653      * Performs the given destroy operation.
37654      * @param {Ext.data.Operation} operation The Operation to perform
37655      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
37656      * @param {Object} scope Scope to execute the callback function in
37657      * @method
37658      */
37659     destroy: Ext.emptyFn,
37660     
37661     /**
37662      * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used internally by
37663      * {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
37664      * <pre><code>
37665      * myProxy.batch({
37666      *     create : [myModel1, myModel2],
37667      *     update : [myModel3],
37668      *     destroy: [myModel4, myModel5]
37669      * });
37670      * </code></pre>
37671      * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and have not been 
37672      * saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been saved but should now be destroyed.
37673      * @param {Object} operations Object containing the Model instances to act upon, keyed by action name
37674      * @param {Object} listeners Optional listeners object passed straight through to the Batch - see {@link Ext.data.Batch}
37675      * @return {Ext.data.Batch} The newly created Ext.data.Batch object
37676      */
37677     batch: function(operations, listeners) {
37678         var me = this,
37679             batch = Ext.create('Ext.data.Batch', {
37680                 proxy: me,
37681                 listeners: listeners || {}
37682             }),
37683             useBatch = me.batchActions, 
37684             records;
37685         
37686         Ext.each(me.batchOrder.split(','), function(action) {
37687             records = operations[action];
37688             if (records) {
37689                 if (useBatch) {
37690                     batch.add(Ext.create('Ext.data.Operation', {
37691                         action: action,
37692                         records: records
37693                     }));
37694                 } else {
37695                     Ext.each(records, function(record){
37696                         batch.add(Ext.create('Ext.data.Operation', {
37697                             action : action, 
37698                             records: [record]
37699                         }));
37700                     });
37701                 }
37702             }
37703         }, me);
37704         
37705         batch.start();
37706         return batch;
37707     }
37708 }, function() {
37709     // Ext.data.proxy.ProxyMgr.registerType('proxy', this);
37710     
37711     //backwards compatibility
37712     Ext.data.DataProxy = this;
37713     // Ext.deprecate('platform', '2.0', function() {
37714     //     Ext.data.DataProxy = this;
37715     // }, this);
37716 });
37717
37718 /**
37719  * @author Ed Spencer
37720  * @class Ext.data.proxy.Server
37721  * @extends Ext.data.proxy.Proxy
37722  * 
37723  * <p>ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy},
37724  * and would not usually be used directly.</p>
37725  * 
37726  * <p>ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been 
37727  * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now an 
37728  * alias of AjaxProxy).</p>
37729  */
37730 Ext.define('Ext.data.proxy.Server', {
37731     extend: 'Ext.data.proxy.Proxy',
37732     alias : 'proxy.server',
37733     alternateClassName: 'Ext.data.ServerProxy',
37734     uses  : ['Ext.data.Request'],
37735     
37736     /**
37737      * @cfg {String} url The URL from which to request the data object.
37738      */
37739     
37740     /**
37741      * @cfg {Object/String/Ext.data.reader.Reader} reader The Ext.data.reader.Reader to use to decode the server's response. This can
37742      * either be a Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
37743      */
37744     
37745     /**
37746      * @cfg {Object/String/Ext.data.writer.Writer} writer The Ext.data.writer.Writer to use to encode any request sent to the server.
37747      * This can either be a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
37748      */
37749     
37750     /**
37751      * @cfg {String} pageParam The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to
37752      * undefined if you don't want to send a page parameter
37753      */
37754     pageParam: 'page',
37755     
37756     /**
37757      * @cfg {String} startParam The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this
37758      * to undefined if you don't want to send a start parameter
37759      */
37760     startParam: 'start',
37761
37762     /**
37763      * @cfg {String} limitParam The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this
37764      * to undefined if you don't want to send a limit parameter
37765      */
37766     limitParam: 'limit',
37767     
37768     /**
37769      * @cfg {String} groupParam The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this
37770      * to undefined if you don't want to send a group parameter
37771      */
37772     groupParam: 'group',
37773     
37774     /**
37775      * @cfg {String} sortParam The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this
37776      * to undefined if you don't want to send a sort parameter
37777      */
37778     sortParam: 'sort',
37779     
37780     /**
37781      * @cfg {String} filterParam The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set 
37782      * this to undefined if you don't want to send a filter parameter
37783      */
37784     filterParam: 'filter',
37785     
37786     /**
37787      * @cfg {String} directionParam The name of the direction parameter to send in a request. <strong>This is only used when simpleSortMode is set to true.</strong>
37788      * Defaults to 'dir'.
37789      */
37790     directionParam: 'dir',
37791     
37792     /**
37793      * @cfg {Boolean} simpleSortMode Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a remote sort is requested.
37794      * The directionParam and sortParam will be sent with the property name and either 'ASC' or 'DESC'
37795      */
37796     simpleSortMode: false,
37797     
37798     /**
37799      * @cfg {Boolean} noCache (optional) Defaults to true. Disable caching by adding a unique parameter
37800      * name to the request.
37801      */
37802     noCache : true,
37803     
37804     /**
37805      * @cfg {String} cacheString The name of the cache param added to the url when using noCache (defaults to "_dc")
37806      */
37807     cacheString: "_dc",
37808     
37809     /**
37810      * @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
37811      */
37812     timeout : 30000,
37813     
37814     /**
37815      * @cfg {Object} api
37816      * Specific urls to call on CRUD action methods "read", "create", "update" and "destroy".
37817      * Defaults to:<pre><code>
37818 api: {
37819     read    : undefined,
37820     create  : undefined,
37821     update  : undefined,
37822     destroy : undefined
37823 }
37824      * </code></pre>
37825      * <p>The url is built based upon the action being executed <tt>[load|create|save|destroy]</tt>
37826      * using the commensurate <tt>{@link #api}</tt> property, or if undefined default to the
37827      * configured {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.</p><br>
37828      * <p>For example:</p>
37829      * <pre><code>
37830 api: {
37831     load :    '/controller/load',
37832     create :  '/controller/new',
37833     save :    '/controller/update',
37834     destroy : '/controller/destroy_action'
37835 }
37836      * </code></pre>
37837      * <p>If the specific URL for a given CRUD action is undefined, the CRUD action request
37838      * will be directed to the configured <tt>{@link Ext.data.proxy.Server#url url}</tt>.</p>
37839      */
37840     
37841     /**
37842      * @ignore
37843      */
37844     constructor: function(config) {
37845         var me = this;
37846         
37847         config = config || {};
37848         this.addEvents(
37849             /**
37850              * @event exception
37851              * Fires when the server returns an exception
37852              * @param {Ext.data.proxy.Proxy} this
37853              * @param {Object} response The response from the AJAX request
37854              * @param {Ext.data.Operation} operation The operation that triggered request
37855              */
37856             'exception'
37857         );
37858         me.callParent([config]);
37859         
37860         /**
37861          * @cfg {Object} extraParams Extra parameters that will be included on every request. Individual requests with params
37862          * of the same name will override these params when they are in conflict.
37863          */
37864         me.extraParams = config.extraParams || {};
37865         
37866         me.api = config.api || {};
37867         
37868         //backwards compatibility, will be deprecated in 5.0
37869         me.nocache = me.noCache;
37870     },
37871     
37872     //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
37873     create: function() {
37874         return this.doRequest.apply(this, arguments);
37875     },
37876     
37877     read: function() {
37878         return this.doRequest.apply(this, arguments);
37879     },
37880     
37881     update: function() {
37882         return this.doRequest.apply(this, arguments);
37883     },
37884     
37885     destroy: function() {
37886         return this.doRequest.apply(this, arguments);
37887     },
37888     
37889     /**
37890      * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
37891      * that this Proxy is attached to.
37892      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
37893      * @return {Ext.data.Request} The request object
37894      */
37895     buildRequest: function(operation) {
37896         var params = Ext.applyIf(operation.params || {}, this.extraParams || {}),
37897             request;
37898         
37899         //copy any sorters, filters etc into the params so they can be sent over the wire
37900         params = Ext.applyIf(params, this.getParams(params, operation));
37901         
37902         if (operation.id && !params.id) {
37903             params.id = operation.id;
37904         }
37905         
37906         request = Ext.create('Ext.data.Request', {
37907             params   : params,
37908             action   : operation.action,
37909             records  : operation.records,
37910             operation: operation,
37911             url      : operation.url
37912         });
37913         
37914         request.url = this.buildUrl(request);
37915         
37916         /*
37917          * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
37918          * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
37919          */
37920         operation.request = request;
37921         
37922         return request;
37923     },
37924     
37925     /**
37926      * 
37927      */
37928     processResponse: function(success, operation, request, response, callback, scope){
37929         var me = this,
37930             reader,
37931             result,
37932             records,
37933             length,
37934             mc,
37935             record,
37936             i;
37937             
37938         if (success === true) {
37939             reader = me.getReader();
37940             result = reader.read(me.extractResponseData(response));
37941             records = result.records;
37942             length = records.length;
37943             
37944             if (result.success !== false) {
37945                 mc = Ext.create('Ext.util.MixedCollection', true, function(r) {return r.getId();});
37946                 mc.addAll(operation.records);
37947                 for (i = 0; i < length; i++) {
37948                     record = mc.get(records[i].getId());
37949                     
37950                     if (record) {
37951                         record.beginEdit();
37952                         record.set(record.data);
37953                         record.endEdit(true);
37954                     }
37955                 }
37956                 
37957                 //see comment in buildRequest for why we include the response object here
37958                 Ext.apply(operation, {
37959                     response: response,
37960                     resultSet: result
37961                 });
37962                 
37963                 operation.setCompleted();
37964                 operation.setSuccessful();
37965             } else {
37966                 operation.setException(result.message);
37967                 me.fireEvent('exception', this, response, operation);
37968             }
37969         } else {
37970             me.setException(operation, response);
37971             me.fireEvent('exception', this, response, operation);              
37972         }
37973             
37974         //this callback is the one that was passed to the 'read' or 'write' function above
37975         if (typeof callback == 'function') {
37976             callback.call(scope || me, operation);
37977         }
37978             
37979         me.afterRequest(request, success);
37980     },
37981     
37982     /**
37983      * Sets up an exception on the operation
37984      * @private
37985      * @param {Ext.data.Operation} operation The operation
37986      * @param {Object} response The response
37987      */
37988     setException: function(operation, response){
37989         operation.setException({
37990             status: response.status,
37991             statusText: response.statusText
37992         });     
37993     },
37994     
37995     /**
37996      * Template method to allow subclasses to specify how to get the response for the reader.
37997      * @private
37998      * @param {Object} response The server response
37999      * @return {Mixed} The response data to be used by the reader
38000      */
38001     extractResponseData: function(response){
38002         return response; 
38003     },
38004     
38005     /**
38006      * Encode any values being sent to the server. Can be overridden in subclasses.
38007      * @private
38008      * @param {Array} An array of sorters/filters.
38009      * @return {Mixed} The encoded value
38010      */
38011     applyEncoding: function(value){
38012         return Ext.encode(value);
38013     },
38014     
38015     /**
38016      * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default, 
38017      * this simply JSON-encodes the sorter data
38018      * @param {Array} sorters The array of {@link Ext.util.Sorter Sorter} objects
38019      * @return {String} The encoded sorters
38020      */
38021     encodeSorters: function(sorters) {
38022         var min = [],
38023             length = sorters.length,
38024             i = 0;
38025         
38026         for (; i < length; i++) {
38027             min[i] = {
38028                 property : sorters[i].property,
38029                 direction: sorters[i].direction
38030             };
38031         }
38032         return this.applyEncoding(min);
38033         
38034     },
38035     
38036     /**
38037      * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default, 
38038      * this simply JSON-encodes the filter data
38039      * @param {Array} sorters The array of {@link Ext.util.Filter Filter} objects
38040      * @return {String} The encoded filters
38041      */
38042     encodeFilters: function(filters) {
38043         var min = [],
38044             length = filters.length,
38045             i = 0;
38046         
38047         for (; i < length; i++) {
38048             min[i] = {
38049                 property: filters[i].property,
38050                 value   : filters[i].value
38051             };
38052         }
38053         return this.applyEncoding(min);
38054     },
38055     
38056     /**
38057      * @private
38058      * Copy any sorters, filters etc into the params so they can be sent over the wire
38059      */
38060     getParams: function(params, operation) {
38061         params = params || {};
38062         
38063         var me             = this,
38064             isDef          = Ext.isDefined,
38065             groupers       = operation.groupers,
38066             sorters        = operation.sorters,
38067             filters        = operation.filters,
38068             page           = operation.page,
38069             start          = operation.start,
38070             limit          = operation.limit,
38071             
38072             simpleSortMode = me.simpleSortMode,
38073             
38074             pageParam      = me.pageParam,
38075             startParam     = me.startParam,
38076             limitParam     = me.limitParam,
38077             groupParam     = me.groupParam,
38078             sortParam      = me.sortParam,
38079             filterParam    = me.filterParam,
38080             directionParam       = me.directionParam;
38081         
38082         if (pageParam && isDef(page)) {
38083             params[pageParam] = page;
38084         }
38085         
38086         if (startParam && isDef(start)) {
38087             params[startParam] = start;
38088         }
38089         
38090         if (limitParam && isDef(limit)) {
38091             params[limitParam] = limit;
38092         }
38093         
38094         if (groupParam && groupers && groupers.length > 0) {
38095             // Grouper is a subclass of sorter, so we can just use the sorter method
38096             params[groupParam] = me.encodeSorters(groupers);
38097         }
38098         
38099         if (sortParam && sorters && sorters.length > 0) {
38100             if (simpleSortMode) {
38101                 params[sortParam] = sorters[0].property;
38102                 params[directionParam] = sorters[0].direction;
38103             } else {
38104                 params[sortParam] = me.encodeSorters(sorters);
38105             }
38106             
38107         }
38108         
38109         if (filterParam && filters && filters.length > 0) {
38110             params[filterParam] = me.encodeFilters(filters);
38111         }
38112         
38113         return params;
38114     },
38115     
38116     /**
38117      * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will
38118      * add the cache-buster param to the end of the url. Subclasses may need to perform additional modifications
38119      * to the url.
38120      * @param {Ext.data.Request} request The request object
38121      * @return {String} The url
38122      */
38123     buildUrl: function(request) {
38124         var me = this,
38125             url = me.getUrl(request);
38126         
38127         if (!url) {
38128             Ext.Error.raise("You are using a ServerProxy but have not supplied it with a url.");
38129         }
38130         
38131         if (me.noCache) {
38132             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
38133         }
38134         
38135         return url;
38136     },
38137     
38138     /**
38139      * Get the url for the request taking into account the order of priority,
38140      * - The request
38141      * - The api
38142      * - The url
38143      * @private
38144      * @param {Ext.data.Request} request The request
38145      * @return {String} The url
38146      */
38147     getUrl: function(request){
38148         return request.url || this.api[request.action] || this.url;
38149     },
38150     
38151     /**
38152      * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all pass
38153      * through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link Ext.data.proxy.JsonP}
38154      * and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as each of the methods that delegate to it.
38155      * @param {Ext.data.Operation} operation The Ext.data.Operation object
38156      * @param {Function} callback The callback function to call when the Operation has completed
38157      * @param {Object} scope The scope in which to execute the callback
38158      */
38159     doRequest: function(operation, callback, scope) {
38160         Ext.Error.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
38161     },
38162     
38163     /**
38164      * Optional callback function which can be used to clean up after a request has been completed.
38165      * @param {Ext.data.Request} request The Request object
38166      * @param {Boolean} success True if the request was successful
38167      * @method
38168      */
38169     afterRequest: Ext.emptyFn,
38170     
38171     onDestroy: function() {
38172         Ext.destroy(this.reader, this.writer);
38173     }
38174 });
38175
38176 /**
38177  * @author Ed Spencer
38178  * @class Ext.data.proxy.Ajax
38179  * @extends Ext.data.proxy.Server
38180  * 
38181  * <p>AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to 
38182  * load data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical
38183  * setup. Here we're going to set up a Store that has an AjaxProxy. To prepare, we'll also set up a 
38184  * {@link Ext.data.Model Model}:</p>
38185  * 
38186 <pre><code>
38187 Ext.define('User', {
38188     extend: 'Ext.data.Model',
38189     fields: ['id', 'name', 'email']
38190 });
38191
38192 //The Store contains the AjaxProxy as an inline configuration
38193 var store = new Ext.data.Store({
38194     model: 'User',
38195     proxy: {
38196         type: 'ajax',
38197         url : 'users.json'
38198     }
38199 });
38200
38201 store.load();
38202 </code></pre>
38203  * 
38204  * <p>Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model}
38205  * with the fields that we expect the server to return. Next we set up the Store itself, along with a {@link #proxy}
38206  * configuration. This configuration was automatically turned into an Ext.data.proxy.Ajax instance, with the url we
38207  * specified being passed into AjaxProxy's constructor. It's as if we'd done this:</p>
38208  * 
38209 <pre><code>
38210 new Ext.data.proxy.Ajax({
38211     url: 'users.json',
38212     model: 'User',
38213     reader: 'json'
38214 });
38215 </code></pre>
38216  * 
38217  * <p>A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default 
38218  * when we create the proxy via the Store - the Store already knows about the Model, and Proxy's default 
38219  * {@link Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.</p>
38220  * 
38221  * <p>Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
38222  * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see {@link #actionMethods}
38223  * to customize this - by default any kind of read will be sent as a GET request and any kind of write will be sent as a
38224  * POST request).</p>
38225  * 
38226  * <p><u>Limitations</u></p>
38227  * 
38228  * <p>AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com
38229  * it cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
38230  * talking to each other via AJAX.</p>
38231  * 
38232  * <p>If you need to read data from another domain and can't set up a proxy server (some software that runs on your own
38233  * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
38234  * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with 
38235  * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
38236  * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.</p>
38237  * 
38238  * <p><u>Readers and Writers</u></p>
38239  * 
38240  * <p>AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response. If
38241  * no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader configuration
38242  * can be passed in as a simple object, which the Proxy automatically turns into a {@link Ext.data.reader.Reader Reader}
38243  * instance:</p>
38244  * 
38245 <pre><code>
38246 var proxy = new Ext.data.proxy.Ajax({
38247     model: 'User',
38248     reader: {
38249         type: 'xml',
38250         root: 'users'
38251     }
38252 });
38253
38254 proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
38255 </code></pre>
38256  * 
38257  * <p><u>Url generation</u></p>
38258  * 
38259  * <p>AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
38260  * each request. These are controlled with the following configuration options:</p>
38261  * 
38262  * <ul style="list-style-type: disc; padding-left: 20px;">
38263  *     <li>{@link #pageParam} - controls how the page number is sent to the server 
38264  *     (see also {@link #startParam} and {@link #limitParam})</li>
38265  *     <li>{@link #sortParam} - controls how sort information is sent to the server</li>
38266  *     <li>{@link #groupParam} - controls how grouping information is sent to the server</li>
38267  *     <li>{@link #filterParam} - controls how filter information is sent to the server</li>
38268  * </ul>
38269  * 
38270  * <p>Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can 
38271  * customize the generated urls, let's say we're loading the Proxy with the following Operation:</p>
38272  * 
38273 <pre><code>
38274 var operation = new Ext.data.Operation({
38275     action: 'read',
38276     page  : 2
38277 });
38278 </code></pre>
38279  * 
38280  * <p>Now we'll issue the request for this Operation by calling {@link #read}:</p>
38281  * 
38282 <pre><code>
38283 var proxy = new Ext.data.proxy.Ajax({
38284     url: '/users'
38285 });
38286
38287 proxy.read(operation); //GET /users?page=2
38288 </code></pre>
38289  * 
38290  * <p>Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is
38291  * sent to the server:</p>
38292  * 
38293 <pre><code>
38294 var proxy = new Ext.data.proxy.Ajax({
38295     url: '/users',
38296     pagePage: 'pageNumber'
38297 });
38298
38299 proxy.read(operation); //GET /users?pageNumber=2
38300 </code></pre>
38301  * 
38302  * <p>Alternatively, our Operation could have been configured to send start and limit parameters instead of page:</p>
38303  * 
38304 <pre><code>
38305 var operation = new Ext.data.Operation({
38306     action: 'read',
38307     start : 50,
38308     limit : 25
38309 });
38310
38311 var proxy = new Ext.data.proxy.Ajax({
38312     url: '/users'
38313 });
38314
38315 proxy.read(operation); //GET /users?start=50&limit=25
38316 </code></pre>
38317  * 
38318  * <p>Again we can customize this url:</p>
38319  * 
38320 <pre><code>
38321 var proxy = new Ext.data.proxy.Ajax({
38322     url: '/users',
38323     startParam: 'startIndex',
38324     limitParam: 'limitIndex'
38325 });
38326
38327 proxy.read(operation); //GET /users?startIndex=50&limitIndex=25
38328 </code></pre>
38329  * 
38330  * <p>AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a
38331  * more expressive Operation object:</p>
38332  * 
38333 <pre><code>
38334 var operation = new Ext.data.Operation({
38335     action: 'read',
38336     sorters: [
38337         new Ext.util.Sorter({
38338             property : 'name',
38339             direction: 'ASC'
38340         }),
38341         new Ext.util.Sorter({
38342             property : 'age',
38343             direction: 'DESC'
38344         })
38345     ],
38346     filters: [
38347         new Ext.util.Filter({
38348             property: 'eyeColor',
38349             value   : 'brown'
38350         })
38351     ]
38352 });
38353 </code></pre>
38354  * 
38355  * <p>This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters
38356  * and filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like
38357  * this (note that the url is escaped before sending the request, but is left unescaped here for clarity):</p>
38358  * 
38359 <pre><code>
38360 var proxy = new Ext.data.proxy.Ajax({
38361     url: '/users'
38362 });
38363
38364 proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter=[{"property":"eyeColor","value":"brown"}]
38365 </code></pre>
38366  * 
38367  * <p>We can again customize how this is created by supplying a few configuration options. Let's say our server is set 
38368  * up to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
38369  * that format like this:</p>
38370  * 
38371  <pre><code>
38372  var proxy = new Ext.data.proxy.Ajax({
38373      url: '/users',
38374      sortParam: 'sortBy',
38375      filterParam: 'filterBy',
38376
38377      //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
38378      encodeSorters: function(sorters) {
38379          var length   = sorters.length,
38380              sortStrs = [],
38381              sorter, i;
38382
38383          for (i = 0; i < length; i++) {
38384              sorter = sorters[i];
38385
38386              sortStrs[i] = sorter.property + '#' + sorter.direction
38387          }
38388
38389          return sortStrs.join(",");
38390      }
38391  });
38392
38393  proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy=[{"property":"eyeColor","value":"brown"}]
38394  </code></pre>
38395  * 
38396  * <p>We can also provide a custom {@link #encodeFilters} function to encode our filters.</p>
38397  * 
38398  * @constructor
38399  * 
38400  * <p>Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the
38401  * Store's call to {@link #load} will override any specified <tt>callback</tt> and <tt>params</tt>
38402  * options. In this case, use the Store's {@link Ext.data.Store#events events} to modify parameters,
38403  * or react to loading events. The Store's {@link Ext.data.Store#baseParams baseParams} may also be
38404  * used to pass parameters known at instantiation time.</p>
38405  * 
38406  * <p>If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make
38407  * the request.</p>
38408  */
38409 Ext.define('Ext.data.proxy.Ajax', {
38410     requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
38411     extend: 'Ext.data.proxy.Server',
38412     alias: 'proxy.ajax',
38413     alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
38414     
38415     /**
38416      * @property actionMethods
38417      * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions and 'POST' 
38418      * for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the correct RESTful methods.
38419      */
38420     actionMethods: {
38421         create : 'POST',
38422         read   : 'GET',
38423         update : 'POST',
38424         destroy: 'POST'
38425     },
38426     
38427     /**
38428      * @cfg {Object} headers Any headers to add to the Ajax request. Defaults to <tt>undefined</tt>.
38429      */
38430     
38431     /**
38432      * @ignore
38433      */
38434     doRequest: function(operation, callback, scope) {
38435         var writer  = this.getWriter(),
38436             request = this.buildRequest(operation, callback, scope);
38437             
38438         if (operation.allowWrite()) {
38439             request = writer.write(request);
38440         }
38441         
38442         Ext.apply(request, {
38443             headers       : this.headers,
38444             timeout       : this.timeout,
38445             scope         : this,
38446             callback      : this.createRequestCallback(request, operation, callback, scope),
38447             method        : this.getMethod(request),
38448             disableCaching: false // explicitly set it to false, ServerProxy handles caching
38449         });
38450         
38451         Ext.Ajax.request(request);
38452         
38453         return request;
38454     },
38455     
38456     /**
38457      * Returns the HTTP method name for a given request. By default this returns based on a lookup on {@link #actionMethods}.
38458      * @param {Ext.data.Request} request The request object
38459      * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
38460      */
38461     getMethod: function(request) {
38462         return this.actionMethods[request.action];
38463     },
38464     
38465     /**
38466      * @private
38467      * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
38468      * of code duplication inside the returned function so we need to find a way to DRY this up.
38469      * @param {Ext.data.Request} request The Request object
38470      * @param {Ext.data.Operation} operation The Operation being executed
38471      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
38472      * passed to doRequest
38473      * @param {Object} scope The scope in which to execute the callback function
38474      * @return {Function} The callback function
38475      */
38476     createRequestCallback: function(request, operation, callback, scope) {
38477         var me = this;
38478         
38479         return function(options, success, response) {
38480             me.processResponse(success, operation, request, response, callback, scope);
38481         };
38482     }
38483 }, function() {
38484     //backwards compatibility, remove in Ext JS 5.0
38485     Ext.data.HttpProxy = this;
38486 });
38487
38488 /**
38489  * @author Ed Spencer
38490  * @class Ext.data.Model
38491  *
38492  * <p>A Model represents some object that your application manages. For example, one might define a Model for Users, Products,
38493  * Cars, or any other real-world object that we want to model in the system. Models are registered via the {@link Ext.ModelManager model manager},
38494  * and are used by {@link Ext.data.Store stores}, which are in turn used by many of the data-bound components in Ext.</p>
38495  *
38496  * <p>Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:</p>
38497  *
38498 <pre><code>
38499 Ext.define('User', {
38500     extend: 'Ext.data.Model',
38501     fields: [
38502         {name: 'name',  type: 'string'},
38503         {name: 'age',   type: 'int'},
38504         {name: 'phone', type: 'string'},
38505         {name: 'alive', type: 'boolean', defaultValue: true}
38506     ],
38507
38508     changeName: function() {
38509         var oldName = this.get('name'),
38510             newName = oldName + " The Barbarian";
38511
38512         this.set('name', newName);
38513     }
38514 });
38515 </code></pre>
38516 *
38517 * <p>The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link Ext.ModelManager ModelManager}, and all
38518 * other functions and properties are copied to the new Model's prototype.</p>
38519 *
38520 * <p>Now we can create instances of our User model and call any model logic we defined:</p>
38521 *
38522 <pre><code>
38523 var user = Ext.ModelManager.create({
38524     name : 'Conan',
38525     age  : 24,
38526     phone: '555-555-5555'
38527 }, 'User');
38528
38529 user.changeName();
38530 user.get('name'); //returns "Conan The Barbarian"
38531 </code></pre>
38532  *
38533  * <p><u>Validations</u></p>
38534  *
38535  * <p>Models have built-in support for validations, which are executed against the validator functions in
38536  * {@link Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to models:</p>
38537  *
38538 <pre><code>
38539 Ext.define('User', {
38540     extend: 'Ext.data.Model',
38541     fields: [
38542         {name: 'name',     type: 'string'},
38543         {name: 'age',      type: 'int'},
38544         {name: 'phone',    type: 'string'},
38545         {name: 'gender',   type: 'string'},
38546         {name: 'username', type: 'string'},
38547         {name: 'alive',    type: 'boolean', defaultValue: true}
38548     ],
38549
38550     validations: [
38551         {type: 'presence',  field: 'age'},
38552         {type: 'length',    field: 'name',     min: 2},
38553         {type: 'inclusion', field: 'gender',   list: ['Male', 'Female']},
38554         {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
38555         {type: 'format',    field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
38556     ]
38557 });
38558 </code></pre>
38559  *
38560  * <p>The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
38561  * object:</p>
38562  *
38563 <pre><code>
38564 var instance = Ext.ModelManager.create({
38565     name: 'Ed',
38566     gender: 'Male',
38567     username: 'edspencer'
38568 }, 'User');
38569
38570 var errors = instance.validate();
38571 </code></pre>
38572  *
38573  * <p><u>Associations</u></p>
38574  *
38575  * <p>Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and
38576  * {@link Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
38577  * application which deals with Users, Posts and Comments. We can express the relationships between these models like this:</p>
38578  *
38579 <pre><code>
38580 Ext.define('Post', {
38581     extend: 'Ext.data.Model',
38582     fields: ['id', 'user_id'],
38583
38584     belongsTo: 'User',
38585     hasMany  : {model: 'Comment', name: 'comments'}
38586 });
38587
38588 Ext.define('Comment', {
38589     extend: 'Ext.data.Model',
38590     fields: ['id', 'user_id', 'post_id'],
38591
38592     belongsTo: 'Post'
38593 });
38594
38595 Ext.define('User', {
38596     extend: 'Ext.data.Model',
38597     fields: ['id'],
38598
38599     hasMany: [
38600         'Post',
38601         {model: 'Comment', name: 'comments'}
38602     ]
38603 });
38604 </code></pre>
38605  *
38606  * <p>See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the usage
38607  * and configuration of associations. Note that associations can also be specified like this:</p>
38608  *
38609 <pre><code>
38610 Ext.define('User', {
38611     extend: 'Ext.data.Model',
38612     fields: ['id'],
38613
38614     associations: [
38615         {type: 'hasMany', model: 'Post',    name: 'posts'},
38616         {type: 'hasMany', model: 'Comment', name: 'comments'}
38617     ]
38618 });
38619 </code></pre>
38620  *
38621  * <p><u>Using a Proxy</u></p>
38622  *
38623  * <p>Models are great for representing types of data and relationships, but sooner or later we're going to want to
38624  * load or save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy},
38625  * which can be set directly on the Model:</p>
38626  *
38627 <pre><code>
38628 Ext.define('User', {
38629     extend: 'Ext.data.Model',
38630     fields: ['id', 'name', 'email'],
38631
38632     proxy: {
38633         type: 'rest',
38634         url : '/users'
38635     }
38636 });
38637 </code></pre>
38638  *
38639  * <p>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
38640  * RESTful backend. Let's see how this works:</p>
38641  *
38642 <pre><code>
38643 var user = Ext.ModelManager.create({name: 'Ed Spencer', email: 'ed@sencha.com'}, 'User');
38644
38645 user.save(); //POST /users
38646 </code></pre>
38647  *
38648  * <p>Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this
38649  * Model's data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't
38650  * have an id, and performs the appropriate action - in this case issuing a POST request to the url we configured
38651  * (/users). We configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full
38652  * list.</p>
38653  *
38654  * <p>Loading data via the Proxy is equally easy:</p>
38655  *
38656 <pre><code>
38657 //get a reference to the User model class
38658 var User = Ext.ModelManager.getModel('User');
38659
38660 //Uses the configured RestProxy to make a GET request to /users/123
38661 User.load(123, {
38662     success: function(user) {
38663         console.log(user.getId()); //logs 123
38664     }
38665 });
38666 </code></pre>
38667  *
38668  * <p>Models can also be updated and destroyed easily:</p>
38669  *
38670 <pre><code>
38671 //the user Model we loaded in the last snippet:
38672 user.set('name', 'Edward Spencer');
38673
38674 //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
38675 user.save({
38676     success: function() {
38677         console.log('The User was updated');
38678     }
38679 });
38680
38681 //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
38682 user.destroy({
38683     success: function() {
38684         console.log('The User was destroyed!');
38685     }
38686 });
38687 </code></pre>
38688  *
38689  * <p><u>Usage in Stores</u></p>
38690  *
38691  * <p>It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this
38692  * by creating a {@link Ext.data.Store Store}:</p>
38693  *
38694 <pre><code>
38695 var store = new Ext.data.Store({
38696     model: 'User'
38697 });
38698
38699 //uses the Proxy we set up on Model to load the Store data
38700 store.load();
38701 </code></pre>
38702  *
38703  * <p>A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain
38704  * a set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the
38705  * {@link Ext.data.Store Store docs} for more information on Stores.</p>
38706  *
38707  * @constructor
38708  * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
38709  * @param {Number} id Optional unique ID to assign to this model instance
38710  */
38711 Ext.define('Ext.data.Model', {
38712     alternateClassName: 'Ext.data.Record',
38713     
38714     mixins: {
38715         observable: 'Ext.util.Observable'
38716     },
38717
38718     requires: [
38719         'Ext.ModelManager',
38720         'Ext.data.Field',
38721         'Ext.data.Errors',
38722         'Ext.data.Operation',
38723         'Ext.data.validations',
38724         'Ext.data.proxy.Ajax',
38725         'Ext.util.MixedCollection'
38726     ],
38727
38728     onClassExtended: function(cls, data) {
38729         var onBeforeClassCreated = data.onBeforeClassCreated;
38730
38731         data.onBeforeClassCreated = function(cls, data) {
38732             var me = this,
38733                 name = Ext.getClassName(cls),
38734                 prototype = cls.prototype,
38735                 superCls = cls.prototype.superclass,
38736
38737                 validations = data.validations || [],
38738                 fields = data.fields || [],
38739                 associations = data.associations || [],
38740                 belongsTo = data.belongsTo,
38741                 hasMany = data.hasMany,
38742
38743                 fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
38744                     return field.name;
38745                 }),
38746
38747                 associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
38748                     return association.name;
38749                 }),
38750
38751                 superValidations = superCls.validations,
38752                 superFields = superCls.fields,
38753                 superAssociations = superCls.associations,
38754
38755                 association, i, ln,
38756                 dependencies = [];
38757
38758             // Save modelName on class and its prototype
38759             cls.modelName = name;
38760             prototype.modelName = name;
38761
38762             // Merge the validations of the superclass and the new subclass
38763             if (superValidations) {
38764                 validations = superValidations.concat(validations);
38765             }
38766
38767             data.validations = validations;
38768
38769             // Merge the fields of the superclass and the new subclass
38770             if (superFields) {
38771                 fields = superFields.items.concat(fields);
38772             }
38773
38774             for (i = 0, ln = fields.length; i < ln; ++i) {
38775                 fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
38776             }
38777
38778             data.fields = fieldsMixedCollection;
38779
38780             //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
38781             //we support that here
38782             if (belongsTo) {
38783                 belongsTo = Ext.Array.from(belongsTo);
38784
38785                 for (i = 0, ln = belongsTo.length; i < ln; ++i) {
38786                     association = belongsTo[i];
38787
38788                     if (!Ext.isObject(association)) {
38789                         association = {model: association};
38790                     }
38791
38792                     association.type = 'belongsTo';
38793                     associations.push(association);
38794                 }
38795
38796                 delete data.belongsTo;
38797             }
38798
38799             if (hasMany) {
38800                 hasMany = Ext.Array.from(hasMany);
38801                 for (i = 0, ln = hasMany.length; i < ln; ++i) {
38802                     association = hasMany[i];
38803
38804                     if (!Ext.isObject(association)) {
38805                         association = {model: association};
38806                     }
38807
38808                     association.type = 'hasMany';
38809                     associations.push(association);
38810                 }
38811
38812                 delete data.hasMany;
38813             }
38814
38815             if (superAssociations) {
38816                 associations = superAssociations.items.concat(associations);
38817             }
38818
38819             for (i = 0, ln = associations.length; i < ln; ++i) {
38820                 dependencies.push('association.' + associations[i].type.toLowerCase());
38821             }
38822
38823             if (data.proxy) {
38824                 if (typeof data.proxy === 'string') {
38825                     dependencies.push('proxy.' + data.proxy);
38826                 }
38827                 else if (typeof data.proxy.type === 'string') {
38828                     dependencies.push('proxy.' + data.proxy.type);
38829                 }
38830             }
38831
38832             Ext.require(dependencies, function() {
38833                 Ext.ModelManager.registerType(name, cls);
38834
38835                 for (i = 0, ln = associations.length; i < ln; ++i) {
38836                     association = associations[i];
38837
38838                     Ext.apply(association, {
38839                         ownerModel: name,
38840                         associatedModel: association.model
38841                     });
38842
38843                     if (Ext.ModelManager.getModel(association.model) === undefined) {
38844                         Ext.ModelManager.registerDeferredAssociation(association);
38845                     } else {
38846                         associationsMixedCollection.add(Ext.data.Association.create(association));
38847                     }
38848                 }
38849
38850                 data.associations = associationsMixedCollection;
38851
38852                 onBeforeClassCreated.call(me, cls, data);
38853
38854                 cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
38855
38856                 // Fire the onModelDefined template method on ModelManager
38857                 Ext.ModelManager.onModelDefined(cls);
38858             });
38859         };
38860     },
38861
38862     inheritableStatics: {
38863         /**
38864          * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias}
38865          * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
38866          * @static
38867          */
38868         setProxy: function(proxy) {
38869             //make sure we have an Ext.data.proxy.Proxy object
38870             if (!proxy.isProxy) {
38871                 if (typeof proxy == "string") {
38872                     proxy = {
38873                         type: proxy
38874                     };
38875                 }
38876                 proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
38877             }
38878             proxy.setModel(this);
38879             this.proxy = this.prototype.proxy = proxy;
38880
38881             return proxy;
38882         },
38883
38884         /**
38885          * Returns the configured Proxy for this Model
38886          * @return {Ext.data.proxy.Proxy} The proxy
38887          */
38888         getProxy: function() {
38889             return this.proxy;
38890         },
38891
38892         /**
38893          * <b>Static</b>. Asynchronously loads a model instance by id. Sample usage:
38894     <pre><code>
38895     MyApp.User = Ext.define('User', {
38896         extend: 'Ext.data.Model',
38897         fields: [
38898             {name: 'id', type: 'int'},
38899             {name: 'name', type: 'string'}
38900         ]
38901     });
38902
38903     MyApp.User.load(10, {
38904         scope: this,
38905         failure: function(record, operation) {
38906             //do something if the load failed
38907         },
38908         success: function(record, operation) {
38909             //do something if the load succeeded
38910         },
38911         callback: function(record, operation) {
38912             //do something whether the load succeeded or failed
38913         }
38914     });
38915     </code></pre>
38916          * @param {Number} id The id of the model to load
38917          * @param {Object} config Optional config object containing success, failure and callback functions, plus optional scope
38918          * @member Ext.data.Model
38919          * @method load
38920          * @static
38921          */
38922         load: function(id, config) {
38923             config = Ext.apply({}, config);
38924             config = Ext.applyIf(config, {
38925                 action: 'read',
38926                 id    : id
38927             });
38928
38929             var operation  = Ext.create('Ext.data.Operation', config),
38930                 scope      = config.scope || this,
38931                 record     = null,
38932                 callback;
38933
38934             callback = function(operation) {
38935                 if (operation.wasSuccessful()) {
38936                     record = operation.getRecords()[0];
38937                     Ext.callback(config.success, scope, [record, operation]);
38938                 } else {
38939                     Ext.callback(config.failure, scope, [record, operation]);
38940                 }
38941                 Ext.callback(config.callback, scope, [record, operation]);
38942             };
38943
38944             this.proxy.read(operation, callback, this);
38945         }
38946     },
38947
38948     statics: {
38949         PREFIX : 'ext-record',
38950         AUTO_ID: 1,
38951         EDIT   : 'edit',
38952         REJECT : 'reject',
38953         COMMIT : 'commit',
38954
38955         /**
38956          * Generates a sequential id. This method is typically called when a record is {@link #create}d
38957          * and {@link #Record no id has been specified}. The id will automatically be assigned
38958          * to the record. The returned id takes the form:
38959          * <tt>&#123;PREFIX}-&#123;AUTO_ID}</tt>.<div class="mdetail-params"><ul>
38960          * <li><b><tt>PREFIX</tt></b> : String<p class="sub-desc"><tt>Ext.data.Model.PREFIX</tt>
38961          * (defaults to <tt>'ext-record'</tt>)</p></li>
38962          * <li><b><tt>AUTO_ID</tt></b> : String<p class="sub-desc"><tt>Ext.data.Model.AUTO_ID</tt>
38963          * (defaults to <tt>1</tt> initially)</p></li>
38964          * </ul></div>
38965          * @param {Ext.data.Model} rec The record being created.  The record does not exist, it's a {@link #phantom}.
38966          * @return {String} auto-generated string id, <tt>"ext-record-i++'</tt>;
38967          * @static
38968          */
38969         id: function(rec) {
38970             var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
38971             rec.phantom = true;
38972             rec.internalId = id;
38973             return id;
38974         }
38975     },
38976     
38977     /**
38978      * Internal flag used to track whether or not the model instance is currently being edited. Read-only
38979      * @property editing
38980      * @type Boolean
38981      */
38982     editing : false,
38983
38984     /**
38985      * Readonly flag - true if this Record has been modified.
38986      * @type Boolean
38987      */
38988     dirty : false,
38989
38990     /**
38991      * @cfg {String} persistanceProperty The property on this Persistable object that its data is saved to.
38992      * Defaults to 'data' (e.g. all persistable data resides in this.data.)
38993      */
38994     persistanceProperty: 'data',
38995
38996     evented: false,
38997     isModel: true,
38998
38999     /**
39000      * <tt>true</tt> when the record does not yet exist in a server-side database (see
39001      * {@link #setDirty}).  Any record which has a real database pk set as its id property
39002      * is NOT a phantom -- it's real.
39003      * @property phantom
39004      * @type {Boolean}
39005      */
39006     phantom : false,
39007
39008     /**
39009      * @cfg {String} idProperty The name of the field treated as this Model's unique id (defaults to 'id').
39010      */
39011     idProperty: 'id',
39012
39013     /**
39014      * The string type of the default Model Proxy. Defaults to 'ajax'
39015      * @property defaultProxyType
39016      * @type String
39017      */
39018     defaultProxyType: 'ajax',
39019
39020     /**
39021      * An array of the fields defined on this model
39022      * @property fields
39023      * @type {Array}
39024      */
39025
39026     // raw not documented intentionally, meant to be used internally.
39027     constructor: function(data, id, raw) {
39028         data = data || {};
39029         
39030         var me = this,
39031             fields,
39032             length,
39033             field,
39034             name,
39035             i,
39036             isArray = Ext.isArray(data),
39037             newData = isArray ? {} : null; // to hold mapped array data if needed
39038
39039         /**
39040          * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
39041          * @property internalId
39042          * @type String
39043          * @private
39044          */
39045         me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
39046         
39047         /**
39048          * The raw data used to create this model if created via a reader.
39049          * @property raw
39050          * @type Object
39051          */
39052         me.raw = raw;
39053
39054         Ext.applyIf(me, {
39055             data: {}    
39056         });
39057         
39058         /**
39059          * Key: value pairs of all fields whose values have changed
39060          * @property modified
39061          * @type Object
39062          */
39063         me.modified = {};
39064
39065         me[me.persistanceProperty] = {};
39066
39067         me.mixins.observable.constructor.call(me);
39068
39069         //add default field values if present
39070         fields = me.fields.items;
39071         length = fields.length;
39072
39073         for (i = 0; i < length; i++) {
39074             field = fields[i];
39075             name  = field.name;
39076
39077             if (isArray){ 
39078                 // Have to map array data so the values get assigned to the named fields
39079                 // rather than getting set as the field names with undefined values.
39080                 newData[name] = data[i];
39081             }
39082             else if (data[name] === undefined) {
39083                 data[name] = field.defaultValue;
39084             }
39085         }
39086
39087         me.set(newData || data);
39088         // clear any dirty/modified since we're initializing
39089         me.dirty = false;
39090         me.modified = {};
39091
39092         if (me.getId()) {
39093             me.phantom = false;
39094         }
39095
39096         if (typeof me.init == 'function') {
39097             me.init();
39098         }
39099
39100         me.id = me.modelName + '-' + me.internalId;
39101
39102         Ext.ModelManager.register(me);
39103     },
39104     
39105     /**
39106      * Returns the value of the given field
39107      * @param {String} fieldName The field to fetch the value for
39108      * @return {Mixed} The value
39109      */
39110     get: function(field) {
39111         return this[this.persistanceProperty][field];
39112     },
39113     
39114     /**
39115      * Sets the given field to the given value, marks the instance as dirty
39116      * @param {String|Object} fieldName The field to set, or an object containing key/value pairs
39117      * @param {Mixed} value The value to set
39118      */
39119     set: function(fieldName, value) {
39120         var me = this,
39121             fields = me.fields,
39122             modified = me.modified,
39123             convertFields = [],
39124             field, key, i, currentValue;
39125
39126         /*
39127          * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
39128          * set those last so that all other possible data is set before the convert function is called
39129          */
39130         if (arguments.length == 1 && Ext.isObject(fieldName)) {
39131             for (key in fieldName) {
39132                 if (fieldName.hasOwnProperty(key)) {
39133                 
39134                     //here we check for the custom convert function. Note that if a field doesn't have a convert function,
39135                     //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
39136                     field = fields.get(key);
39137                     if (field && field.convert !== field.type.convert) {
39138                         convertFields.push(key);
39139                         continue;
39140                     }
39141                     
39142                     me.set(key, fieldName[key]);
39143                 }
39144             }
39145
39146             for (i = 0; i < convertFields.length; i++) {
39147                 field = convertFields[i];
39148                 me.set(field, fieldName[field]);
39149             }
39150
39151         } else {
39152             if (fields) {
39153                 field = fields.get(fieldName);
39154
39155                 if (field && field.convert) {
39156                     value = field.convert(value, me);
39157                 }
39158             }
39159             currentValue = me.get(fieldName);
39160             me[me.persistanceProperty][fieldName] = value;
39161             
39162             if (field && field.persist && !me.isEqual(currentValue, value)) {
39163                 me.dirty = true;
39164                 me.modified[fieldName] = currentValue;
39165             }
39166
39167             if (!me.editing) {
39168                 me.afterEdit();
39169             }
39170         }
39171     },
39172     
39173     /**
39174      * Checks if two values are equal, taking into account certain
39175      * special factors, for example dates.
39176      * @private
39177      * @param {Object} a The first value
39178      * @param {Object} b The second value
39179      * @return {Boolean} True if the values are equal
39180      */
39181     isEqual: function(a, b){
39182         if (Ext.isDate(a) && Ext.isDate(b)) {
39183             return a.getTime() === b.getTime();
39184         }
39185         return a === b;
39186     },
39187     
39188     /**
39189      * Begin an edit. While in edit mode, no events (e.g.. the <code>update</code> event)
39190      * are relayed to the containing store. When an edit has begun, it must be followed
39191      * by either {@link #endEdit} or {@link #cancelEdit}.
39192      */
39193     beginEdit : function(){
39194         var me = this;
39195         if (!me.editing) {
39196             me.editing = true;
39197             me.dirtySave = me.dirty;
39198             me.dataSave = Ext.apply({}, me[me.persistanceProperty]);
39199             me.modifiedSave = Ext.apply({}, me.modified);
39200         }
39201     },
39202     
39203     /**
39204      * Cancels all changes made in the current edit operation.
39205      */
39206     cancelEdit : function(){
39207         var me = this;
39208         if (me.editing) {
39209             me.editing = false;
39210             // reset the modified state, nothing changed since the edit began
39211             me.modified = me.modifiedSave;
39212             me[me.persistanceProperty] = me.dataSave;
39213             me.dirty = me.dirtySave;
39214             delete me.modifiedSave;
39215             delete me.dataSave;
39216             delete me.dirtySave;
39217         }
39218     },
39219     
39220     /**
39221      * End an edit. If any data was modified, the containing store is notified
39222      * (ie, the store's <code>update</code> event will fire).
39223      * @param {Boolean} silent True to not notify the store of the change
39224      */
39225     endEdit : function(silent){
39226         var me = this;
39227         if (me.editing) {
39228             me.editing = false;
39229             delete me.modifiedSave;
39230             delete me.dataSave;
39231             delete me.dirtySave;
39232             if (silent !== true && me.dirty) {
39233                 me.afterEdit();
39234             }
39235         }
39236     },
39237     
39238     /**
39239      * Gets a hash of only the fields that have been modified since this Model was created or commited.
39240      * @return Object
39241      */
39242     getChanges : function(){
39243         var modified = this.modified,
39244             changes  = {},
39245             field;
39246
39247         for (field in modified) {
39248             if (modified.hasOwnProperty(field)){
39249                 changes[field] = this.get(field);
39250             }
39251         }
39252
39253         return changes;
39254     },
39255     
39256     /**
39257      * Returns <tt>true</tt> if the passed field name has been <code>{@link #modified}</code>
39258      * since the load or last commit.
39259      * @param {String} fieldName {@link Ext.data.Field#name}
39260      * @return {Boolean}
39261      */
39262     isModified : function(fieldName) {
39263         return this.modified.hasOwnProperty(fieldName);
39264     },
39265     
39266     /**
39267      * <p>Marks this <b>Record</b> as <code>{@link #dirty}</code>.  This method
39268      * is used interally when adding <code>{@link #phantom}</code> records to a
39269      * {@link Ext.data.Store#writer writer enabled store}.</p>
39270      * <br><p>Marking a record <code>{@link #dirty}</code> causes the phantom to
39271      * be returned by {@link Ext.data.Store#getModifiedRecords} where it will
39272      * have a create action composed for it during {@link Ext.data.Store#save store save}
39273      * operations.</p>
39274      */
39275     setDirty : function() {
39276         var me = this,
39277             name;
39278         
39279         me.dirty = true;
39280
39281         me.fields.each(function(field) {
39282             if (field.persist) {
39283                 name = field.name;
39284                 me.modified[name] = me.get(name);
39285             }
39286         }, me);
39287     },
39288
39289     markDirty : function() {
39290         if (Ext.isDefined(Ext.global.console)) {
39291             Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
39292         }
39293         return this.setDirty.apply(this, arguments);
39294     },
39295     
39296     /**
39297      * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}.
39298      * Rejects all changes made to the model instance since either creation, or the last commit operation.
39299      * Modified fields are reverted to their original values.
39300      * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
39301      * to have their code notified of reject operations.</p>
39302      * @param {Boolean} silent (optional) True to skip notification of the owning
39303      * store of the change (defaults to false)
39304      */
39305     reject : function(silent) {
39306         var me = this,
39307             modified = me.modified,
39308             field;
39309
39310         for (field in modified) {
39311             if (modified.hasOwnProperty(field)) {
39312                 if (typeof modified[field] != "function") {
39313                     me[me.persistanceProperty][field] = modified[field];
39314                 }
39315             }
39316         }
39317
39318         me.dirty = false;
39319         me.editing = false;
39320         me.modified = {};
39321
39322         if (silent !== true) {
39323             me.afterReject();
39324         }
39325     },
39326
39327     /**
39328      * Usually called by the {@link Ext.data.Store} which owns the model instance.
39329      * Commits all changes made to the instance since either creation or the last commit operation.
39330      * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
39331      * to have their code notified of commit operations.</p>
39332      * @param {Boolean} silent (optional) True to skip notification of the owning
39333      * store of the change (defaults to false)
39334      */
39335     commit : function(silent) {
39336         var me = this;
39337         
39338         me.dirty = false;
39339         me.editing = false;
39340
39341         me.modified = {};
39342
39343         if (silent !== true) {
39344             me.afterCommit();
39345         }
39346     },
39347
39348     /**
39349      * Creates a copy (clone) of this Model instance.
39350      * @param {String} id (optional) A new id, defaults to the id
39351      * of the instance being copied. See <code>{@link #id}</code>.
39352      * To generate a phantom instance with a new id use:<pre><code>
39353 var rec = record.copy(); // clone the record
39354 Ext.data.Model.id(rec); // automatically generate a unique sequential id
39355      * </code></pre>
39356      * @return {Record}
39357      */
39358     copy : function(newId) {
39359         var me = this;
39360         
39361         return new me.self(Ext.apply({}, me[me.persistanceProperty]), newId || me.internalId);
39362     },
39363
39364     /**
39365      * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias}
39366      * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
39367      * @static
39368      */
39369     setProxy: function(proxy) {
39370         //make sure we have an Ext.data.proxy.Proxy object
39371         if (!proxy.isProxy) {
39372             if (typeof proxy === "string") {
39373                 proxy = {
39374                     type: proxy
39375                 };
39376             }
39377             proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
39378         }
39379         proxy.setModel(this.self);
39380         this.proxy = proxy;
39381
39382         return proxy;
39383     },
39384
39385     /**
39386      * Returns the configured Proxy for this Model
39387      * @return {Ext.data.proxy.Proxy} The proxy
39388      */
39389     getProxy: function() {
39390         return this.proxy;
39391     },
39392
39393     /**
39394      * Validates the current data against all of its configured {@link #validations} and returns an
39395      * {@link Ext.data.Errors Errors} object
39396      * @return {Ext.data.Errors} The errors object
39397      */
39398     validate: function() {
39399         var errors      = Ext.create('Ext.data.Errors'),
39400             validations = this.validations,
39401             validators  = Ext.data.validations,
39402             length, validation, field, valid, type, i;
39403
39404         if (validations) {
39405             length = validations.length;
39406
39407             for (i = 0; i < length; i++) {
39408                 validation = validations[i];
39409                 field = validation.field || validation.name;
39410                 type  = validation.type;
39411                 valid = validators[type](validation, this.get(field));
39412
39413                 if (!valid) {
39414                     errors.add({
39415                         field  : field,
39416                         message: validation.message || validators[type + 'Message']
39417                     });
39418                 }
39419             }
39420         }
39421
39422         return errors;
39423     },
39424
39425     /**
39426      * Checks if the model is valid. See {@link #validate}.
39427      * @return {Boolean} True if the model is valid.
39428      */
39429     isValid: function(){
39430         return this.validate().isValid();
39431     },
39432
39433     /**
39434      * Saves the model instance using the configured proxy
39435      * @param {Object} options Options to pass to the proxy
39436      * @return {Ext.data.Model} The Model instance
39437      */
39438     save: function(options) {
39439         options = Ext.apply({}, options);
39440
39441         var me     = this,
39442             action = me.phantom ? 'create' : 'update',
39443             record = null,
39444             scope  = options.scope || me,
39445             operation,
39446             callback;
39447
39448         Ext.apply(options, {
39449             records: [me],
39450             action : action
39451         });
39452
39453         operation = Ext.create('Ext.data.Operation', options);
39454
39455         callback = function(operation) {
39456             if (operation.wasSuccessful()) {
39457                 record = operation.getRecords()[0];
39458                 //we need to make sure we've set the updated data here. Ideally this will be redundant once the
39459                 //ModelCache is in place
39460                 me.set(record.data);
39461                 record.dirty = false;
39462
39463                 Ext.callback(options.success, scope, [record, operation]);
39464             } else {
39465                 Ext.callback(options.failure, scope, [record, operation]);
39466             }
39467
39468             Ext.callback(options.callback, scope, [record, operation]);
39469         };
39470
39471         me.getProxy()[action](operation, callback, me);
39472
39473         return me;
39474     },
39475
39476     /**
39477      * Destroys the model using the configured proxy
39478      * @param {Object} options Options to pass to the proxy
39479      * @return {Ext.data.Model} The Model instance
39480      */
39481     destroy: function(options){
39482         options = Ext.apply({}, options);
39483
39484         var me     = this,
39485             record = null,
39486             scope  = options.scope || me,
39487             operation,
39488             callback;
39489
39490         Ext.apply(options, {
39491             records: [me],
39492             action : 'destroy'
39493         });
39494
39495         operation = Ext.create('Ext.data.Operation', options);
39496         callback = function(operation) {
39497             if (operation.wasSuccessful()) {
39498                 Ext.callback(options.success, scope, [record, operation]);
39499             } else {
39500                 Ext.callback(options.failure, scope, [record, operation]);
39501             }
39502             Ext.callback(options.callback, scope, [record, operation]);
39503         };
39504
39505         me.getProxy().destroy(operation, callback, me);
39506         return me;
39507     },
39508
39509     /**
39510      * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}
39511      * @return {Number} The id
39512      */
39513     getId: function() {
39514         return this.get(this.idProperty);
39515     },
39516
39517     /**
39518      * Sets the model instance's id field to the given id
39519      * @param {Number} id The new id
39520      */
39521     setId: function(id) {
39522         this.set(this.idProperty, id);
39523     },
39524
39525     /**
39526      * Tells this model instance that it has been added to a store
39527      * @param {Ext.data.Store} store The store that the model has been added to
39528      */
39529     join : function(store) {
39530         /**
39531          * The {@link Ext.data.Store} to which this Record belongs.
39532          * @property store
39533          * @type {Ext.data.Store}
39534          */
39535         this.store = store;
39536     },
39537
39538     /**
39539      * Tells this model instance that it has been removed from the store
39540      */
39541     unjoin: function() {
39542         delete this.store;
39543     },
39544
39545     /**
39546      * @private
39547      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
39548      * afterEdit method is called
39549      */
39550     afterEdit : function() {
39551         this.callStore('afterEdit');
39552     },
39553
39554     /**
39555      * @private
39556      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
39557      * afterReject method is called
39558      */
39559     afterReject : function() {
39560         this.callStore("afterReject");
39561     },
39562
39563     /**
39564      * @private
39565      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
39566      * afterCommit method is called
39567      */
39568     afterCommit: function() {
39569         this.callStore('afterCommit');
39570     },
39571
39572     /**
39573      * @private
39574      * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
39575      * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
39576      * will always be called with the model instance as its single argument.
39577      * @param {String} fn The function to call on the store
39578      */
39579     callStore: function(fn) {
39580         var store = this.store;
39581
39582         if (store !== undefined && typeof store[fn] == "function") {
39583             store[fn](this);
39584         }
39585     },
39586
39587     /**
39588      * Gets all of the data from this Models *loaded* associations.
39589      * It does this recursively - for example if we have a User which
39590      * hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
39591      * {
39592      *     orders: [
39593      *         {
39594      *             id: 123,
39595      *             status: 'shipped',
39596      *             orderItems: [
39597      *                 ...
39598      *             ]
39599      *         }
39600      *     ]
39601      * }
39602      * @return {Object} The nested data set for the Model's loaded associations
39603      */
39604     getAssociatedData: function(){
39605         return this.prepareAssociatedData(this, [], null);
39606     },
39607
39608     /**
39609      * @private
39610      * This complex-looking method takes a given Model instance and returns an object containing all data from
39611      * all of that Model's *loaded* associations. See (@link #getAssociatedData}
39612      * @param {Ext.data.Model} record The Model instance
39613      * @param {Array} ids PRIVATE. The set of Model instance internalIds that have already been loaded
39614      * @param {String} associationType (optional) The name of the type of association to limit to.
39615      * @return {Object} The nested data set for the Model's loaded associations
39616      */
39617     prepareAssociatedData: function(record, ids, associationType) {
39618         //we keep track of all of the internalIds of the models that we have loaded so far in here
39619         var associations     = record.associations.items,
39620             associationCount = associations.length,
39621             associationData  = {},
39622             associatedStore, associatedName, associatedRecords, associatedRecord,
39623             associatedRecordCount, association, id, i, j, type, allow;
39624
39625         for (i = 0; i < associationCount; i++) {
39626             association = associations[i];
39627             type = association.type;
39628             allow = true;
39629             if (associationType) {
39630                 allow = type == associationType;
39631             }
39632             if (allow && type == 'hasMany') {
39633
39634                 //this is the hasMany store filled with the associated data
39635                 associatedStore = record[association.storeName];
39636
39637                 //we will use this to contain each associated record's data
39638                 associationData[association.name] = [];
39639
39640                 //if it's loaded, put it into the association data
39641                 if (associatedStore && associatedStore.data.length > 0) {
39642                     associatedRecords = associatedStore.data.items;
39643                     associatedRecordCount = associatedRecords.length;
39644
39645                     //now we're finally iterating over the records in the association. We do this recursively
39646                     for (j = 0; j < associatedRecordCount; j++) {
39647                         associatedRecord = associatedRecords[j];
39648                         // Use the id, since it is prefixed with the model name, guaranteed to be unique
39649                         id = associatedRecord.id;
39650
39651                         //when we load the associations for a specific model instance we add it to the set of loaded ids so that
39652                         //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
39653                         if (Ext.Array.indexOf(ids, id) == -1) {
39654                             ids.push(id);
39655
39656                             associationData[association.name][j] = associatedRecord.data;
39657                             Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
39658                         }
39659                     }
39660                 }
39661             } else if (allow && type == 'belongsTo') {
39662                 associatedRecord = record[association.instanceName];
39663                 if (associatedRecord !== undefined) {
39664                     id = associatedRecord.id;
39665                     if (Ext.Array.indexOf(ids, id) == -1) {
39666                         ids.push(id);
39667                         associationData[association.name] = associatedRecord.data;
39668                         Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
39669                     }
39670                 }
39671             }
39672         }
39673
39674         return associationData;
39675     }
39676 });
39677
39678 /**
39679  * @class Ext.Component
39680  * @extends Ext.AbstractComponent
39681  * <p>Base class for all Ext components.  All subclasses of Component may participate in the automated
39682  * Ext component lifecycle of creation, rendering and destruction which is provided by the {@link Ext.container.Container Container} class.
39683  * Components may be added to a Container through the {@link Ext.container.Container#items items} config option at the time the Container is created,
39684  * or they may be added dynamically via the {@link Ext.container.Container#add add} method.</p>
39685  * <p>The Component base class has built-in support for basic hide/show and enable/disable and size control behavior.</p>
39686  * <p>All Components are registered with the {@link Ext.ComponentManager} on construction so that they can be referenced at any time via
39687  * {@link Ext#getCmp Ext.getCmp}, passing the {@link #id}.</p>
39688  * <p>All user-developed visual widgets that are required to participate in automated lifecycle and size management should subclass Component.</p>
39689  * <p>See the <a href="http://sencha.com/learn/Tutorial:Creating_new_UI_controls">Creating new UI controls</a> tutorial for details on how
39690  * and to either extend or augment ExtJs base classes to create custom Components.</p>
39691  * <p>Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the
39692  * xtype like {@link #getXType} and {@link #isXType}. This is the list of all valid xtypes:</p>
39693  * <pre>
39694 xtype            Class
39695 -------------    ------------------
39696 button           {@link Ext.button.Button}
39697 buttongroup      {@link Ext.container.ButtonGroup}
39698 colorpalette     {@link Ext.picker.Color}
39699 component        {@link Ext.Component}
39700 container        {@link Ext.container.Container}
39701 cycle            {@link Ext.button.Cycle}
39702 dataview         {@link Ext.view.View}
39703 datepicker       {@link Ext.picker.Date}
39704 editor           {@link Ext.Editor}
39705 editorgrid       {@link Ext.grid.plugin.Editing}
39706 grid             {@link Ext.grid.Panel}
39707 multislider      {@link Ext.slider.Multi}
39708 panel            {@link Ext.panel.Panel}
39709 progress         {@link Ext.ProgressBar}
39710 slider           {@link Ext.slider.Single}
39711 spacer           {@link Ext.toolbar.Spacer}
39712 splitbutton      {@link Ext.button.Split}
39713 tabpanel         {@link Ext.tab.Panel}
39714 treepanel        {@link Ext.tree.Panel}
39715 viewport         {@link Ext.container.Viewport}
39716 window           {@link Ext.window.Window}
39717
39718 Toolbar components
39719 ---------------------------------------
39720 paging           {@link Ext.toolbar.Paging}
39721 toolbar          {@link Ext.toolbar.Toolbar}
39722 tbfill           {@link Ext.toolbar.Fill}
39723 tbitem           {@link Ext.toolbar.Item}
39724 tbseparator      {@link Ext.toolbar.Separator}
39725 tbspacer         {@link Ext.toolbar.Spacer}
39726 tbtext           {@link Ext.toolbar.TextItem}
39727
39728 Menu components
39729 ---------------------------------------
39730 menu             {@link Ext.menu.Menu}
39731 menucheckitem    {@link Ext.menu.CheckItem}
39732 menuitem         {@link Ext.menu.Item}
39733 menuseparator    {@link Ext.menu.Separator}
39734 menutextitem     {@link Ext.menu.Item}
39735
39736 Form components
39737 ---------------------------------------
39738 form             {@link Ext.form.Panel}
39739 checkbox         {@link Ext.form.field.Checkbox}
39740 combo            {@link Ext.form.field.ComboBox}
39741 datefield        {@link Ext.form.field.Date}
39742 displayfield     {@link Ext.form.field.Display}
39743 field            {@link Ext.form.field.Base}
39744 fieldset         {@link Ext.form.FieldSet}
39745 hidden           {@link Ext.form.field.Hidden}
39746 htmleditor       {@link Ext.form.field.HtmlEditor}
39747 label            {@link Ext.form.Label}
39748 numberfield      {@link Ext.form.field.Number}
39749 radio            {@link Ext.form.field.Radio}
39750 radiogroup       {@link Ext.form.RadioGroup}
39751 textarea         {@link Ext.form.field.TextArea}
39752 textfield        {@link Ext.form.field.Text}
39753 timefield        {@link Ext.form.field.Time}
39754 trigger          {@link Ext.form.field.Trigger}
39755
39756 Chart components
39757 ---------------------------------------
39758 chart            {@link Ext.chart.Chart}
39759 barchart         {@link Ext.chart.series.Bar}
39760 columnchart      {@link Ext.chart.series.Column}
39761 linechart        {@link Ext.chart.series.Line}
39762 piechart         {@link Ext.chart.series.Pie}
39763
39764 </pre><p>
39765  * It should not usually be necessary to instantiate a Component because there are provided subclasses which implement specialized Component
39766  * use cases which over most application needs. However it is possible to instantiate a base Component, and it will be renderable,
39767  * or will particpate in layouts as the child item of a Container:
39768 {@img Ext.Component/Ext.Component.png Ext.Component component}
39769 <pre><code>
39770     Ext.create('Ext.Component', {
39771         html: 'Hello world!',
39772         width: 300,
39773         height: 200,
39774         padding: 20,
39775         style: {
39776             color: '#FFFFFF',
39777             backgroundColor:'#000000'
39778         },
39779         renderTo: Ext.getBody()
39780     });
39781 </code></pre>
39782  *</p>
39783  *<p>The Component above creates its encapsulating <code>div</code> upon render, and use the configured HTML as content. More complex
39784  * internal structure may be created using the {@link #renderTpl} configuration, although to display database-derived mass
39785  * data, it is recommended that an ExtJS data-backed Component such as a {Ext.view.DataView DataView}, or {Ext.grid.Panel GridPanel},
39786  * or {@link Ext.tree.Panel TreePanel} be used.</p>
39787  * @constructor
39788  * @param {Ext.core.Element/String/Object} config The configuration options may be specified as either:
39789  * <div class="mdetail-params"><ul>
39790  * <li><b>an element</b> :
39791  * <p class="sub-desc">it is set as the internal element and its id used as the component id</p></li>
39792  * <li><b>a string</b> :
39793  * <p class="sub-desc">it is assumed to be the id of an existing element and is used as the component id</p></li>
39794  * <li><b>anything else</b> :
39795  * <p class="sub-desc">it is assumed to be a standard config object and is applied to the component</p></li>
39796  * </ul></div>
39797  */
39798
39799 Ext.define('Ext.Component', {
39800
39801     /* Begin Definitions */
39802
39803     alias: ['widget.component', 'widget.box'],
39804
39805     extend: 'Ext.AbstractComponent',
39806
39807     requires: [
39808         'Ext.util.DelayedTask'
39809     ],
39810
39811     uses: [
39812         'Ext.Layer',
39813         'Ext.resizer.Resizer',
39814         'Ext.util.ComponentDragger'
39815     ],
39816
39817     mixins: {
39818         floating: 'Ext.util.Floating'
39819     },
39820
39821     statics: {
39822         // Collapse/expand directions
39823         DIRECTION_TOP: 'top',
39824         DIRECTION_RIGHT: 'right',
39825         DIRECTION_BOTTOM: 'bottom',
39826         DIRECTION_LEFT: 'left'
39827     },
39828
39829     /* End Definitions */
39830
39831     /**
39832      * @cfg {Mixed} resizable
39833      * <p>Specify as <code>true</code> to apply a {@link Ext.resizer.Resizer Resizer} to this Component
39834      * after rendering.</p>
39835      * <p>May also be specified as a config object to be passed to the constructor of {@link Ext.resizer.Resizer Resizer}
39836      * to override any defaults. By default the Component passes its minimum and maximum size, and uses
39837      * <code>{@link Ext.resizer.Resizer#dynamic}: false</code></p>
39838      */
39839
39840     /**
39841      * @cfg {String} resizeHandles
39842      * A valid {@link Ext.resizer.Resizer} handles config string (defaults to 'all').  Only applies when resizable = true.
39843      */
39844     resizeHandles: 'all',
39845
39846     /**
39847      * @cfg {Boolean} autoScroll
39848      * <code>true</code> to use overflow:'auto' on the components layout element and show scroll bars automatically when
39849      * necessary, <code>false</code> to clip any overflowing content (defaults to <code>false</code>).
39850      */
39851
39852     /**
39853      * @cfg {Boolean} floating
39854      * <p>Specify as true to float the Component outside of the document flow using CSS absolute positioning.</p>
39855      * <p>Components such as {@link Ext.window.Window Window}s and {@link Ext.menu.Menu Menu}s are floating
39856      * by default.</p>
39857      * <p>Floating Components that are programatically {@link Ext.Component#render rendered} will register themselves with the global
39858      * {@link Ext.WindowManager ZIndexManager}</p>
39859      * <h3 class="pa">Floating Components as child items of a Container</h3>
39860      * <p>A floating Component may be used as a child item of a Container. This just allows the floating Component to seek a ZIndexManager by
39861      * examining the ownerCt chain.</p>
39862      * <p>When configured as floating, Components acquire, at render time, a {@link Ext.ZIndexManager ZIndexManager} which manages a stack
39863      * of related floating Components. The ZIndexManager brings a single floating Component to the top of its stack when
39864      * the Component's {@link #toFront} method is called.</p>
39865      * <p>The ZIndexManager is found by traversing up the {@link #ownerCt} chain to find an ancestor which itself is floating. This is so that
39866      * descendant floating Components of floating <i>Containers</i> (Such as a ComboBox dropdown within a Window) can have its zIndex managed relative
39867      * to any siblings, but always <b>above</b> that floating ancestor Container.</p>
39868      * <p>If no floating ancestor is found, a floating Component registers itself with the default {@link Ext.WindowManager ZIndexManager}.</p>
39869      * <p>Floating components <i>do not participate in the Container's layout</i>. Because of this, they are not rendered until you explicitly
39870      * {@link #show} them.</p>
39871      * <p>After rendering, the ownerCt reference is deleted, and the {@link #floatParent} property is set to the found floating ancestor Container.
39872      * If no floating ancestor Container was found the {@link #floatParent} property will not be set.</p>
39873      */
39874     floating: false,
39875
39876     /**
39877      * @cfg {Boolean} toFrontOnShow
39878      * <p>True to automatically call {@link #toFront} when the {@link #show} method is called
39879      * on an already visible, floating component (default is <code>true</code>).</p>
39880      */
39881     toFrontOnShow: true,
39882
39883     /**
39884      * <p>Optional. Only present for {@link #floating} Components after they have been rendered.</p>
39885      * <p>A reference to the ZIndexManager which is managing this Component's z-index.</p>
39886      * <p>The {@link Ext.ZIndexManager ZIndexManager} maintains a stack of floating Component z-indices, and also provides a single modal
39887      * mask which is insert just beneath the topmost visible modal floating Component.</p>
39888      * <p>Floating Components may be {@link #toFront brought to the front} or {@link #toBack sent to the back} of the z-index stack.</p>
39889      * <p>This defaults to the global {@link Ext.WindowManager ZIndexManager} for floating Components that are programatically
39890      * {@link Ext.Component#render rendered}.</p>
39891      * <p>For {@link #floating} Components which are added to a Container, the ZIndexManager is acquired from the first ancestor Container found
39892      * which is floating, or if not found the global {@link Ext.WindowManager ZIndexManager} is used.</p>
39893      * <p>See {@link #floating} and {@link #floatParent}</p>
39894      * @property zIndexManager
39895      * @type Ext.ZIndexManager
39896      */
39897
39898      /**
39899       * <p>Optional. Only present for {@link #floating} Components which were inserted as descendant items of floating Containers.</p>
39900       * <p>Floating Components that are programatically {@link Ext.Component#render rendered} will not have a <code>floatParent</code> property.</p>
39901       * <p>For {@link #floating} Components which are child items of a Container, the floatParent will be the floating ancestor Container which is
39902       * responsible for the base z-index value of all its floating descendants. It provides a {@link Ext.ZIndexManager ZIndexManager} which provides
39903       * z-indexing services for all its descendant floating Components.</p>
39904       * <p>For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the Window as its
39905       * <code>floatParent</code></p>
39906       * <p>See {@link #floating} and {@link #zIndexManager}</p>
39907       * @property floatParent
39908       * @type Ext.Container
39909       */
39910
39911     /**
39912      * @cfg {Mixed} draggable
39913      * <p>Specify as true to make a {@link #floating} Component draggable using the Component's encapsulating element as the drag handle.</p>
39914      * <p>This may also be specified as a config object for the {@link Ext.util.ComponentDragger ComponentDragger} which is instantiated to perform dragging.</p>
39915      * <p>For example to create a Component which may only be dragged around using a certain internal element as the drag handle,
39916      * use the delegate option:</p>
39917      * <code><pre>
39918 new Ext.Component({
39919     constrain: true,
39920     floating:true,
39921     style: {
39922         backgroundColor: '#fff',
39923         border: '1px solid black'
39924     },
39925     html: '&lt;h1 style="cursor:move"&gt;The title&lt;/h1&gt;&lt;p&gt;The content&lt;/p&gt;',
39926     draggable: {
39927         delegate: 'h1'
39928     }
39929 }).show();
39930 </pre></code>
39931      */
39932
39933     /**
39934      * @cfg {Boolean} maintainFlex
39935      * <p><b>Only valid when a sibling element of a {@link Ext.resizer.Splitter Splitter} within a {@link Ext.layout.container.VBox VBox} or
39936      * {@link Ext.layout.container.HBox HBox} layout.</b></p>
39937      * <p>Specifies that if an immediate sibling Splitter is moved, the Component on the <i>other</i> side is resized, and this
39938      * Component maintains its configured {@link Ext.layout.container.Box#flex flex} value.</p>
39939      */
39940
39941     hideMode: 'display',
39942     // Deprecate 5.0
39943     hideParent: false,
39944
39945     ariaRole: 'presentation',
39946
39947     bubbleEvents: [],
39948
39949     actionMode: 'el',
39950     monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
39951
39952     //renderTpl: new Ext.XTemplate(
39953     //    '<div id="{id}" class="{baseCls} {cls} {cmpCls}<tpl if="typeof ui !== \'undefined\'"> {uiBase}-{ui}</tpl>"<tpl if="typeof style !== \'undefined\'"> style="{style}"</tpl>></div>', {
39954     //        compiled: true,
39955     //        disableFormats: true
39956     //    }
39957     //),
39958     constructor: function(config) {
39959         config = config || {};
39960         if (config.initialConfig) {
39961
39962             // Being initialized from an Ext.Action instance...
39963             if (config.isAction) {
39964                 this.baseAction = config;
39965             }
39966             config = config.initialConfig;
39967             // component cloning / action set up
39968         }
39969         else if (config.tagName || config.dom || Ext.isString(config)) {
39970             // element object
39971             config = {
39972                 applyTo: config,
39973                 id: config.id || config
39974             };
39975         }
39976
39977         this.callParent([config]);
39978
39979         // If we were configured from an instance of Ext.Action, (or configured with a baseAction option),
39980         // register this Component as one of its items
39981         if (this.baseAction){
39982             this.baseAction.addComponent(this);
39983         }
39984     },
39985
39986     initComponent: function() {
39987         var me = this;
39988
39989         if (me.listeners) {
39990             me.on(me.listeners);
39991             delete me.listeners;
39992         }
39993         me.enableBubble(me.bubbleEvents);
39994         me.mons = [];
39995     },
39996
39997     // private
39998     afterRender: function() {
39999         var me = this,
40000             resizable = me.resizable;
40001
40002         if (me.floating) {
40003             me.makeFloating(me.floating);
40004         } else {
40005             me.el.setVisibilityMode(Ext.core.Element[me.hideMode.toUpperCase()]);
40006         }
40007
40008         if (Ext.isDefined(me.autoScroll)) {
40009             me.setAutoScroll(me.autoScroll);
40010         }
40011         me.callParent();
40012
40013         if (!(me.x && me.y) && (me.pageX || me.pageY)) {
40014             me.setPagePosition(me.pageX, me.pageY);
40015         }
40016
40017         if (resizable) {
40018             me.initResizable(resizable);
40019         }
40020
40021         if (me.draggable) {
40022             me.initDraggable();
40023         }
40024
40025         me.initAria();
40026     },
40027
40028     initAria: function() {
40029         var actionEl = this.getActionEl(),
40030             role = this.ariaRole;
40031         if (role) {
40032             actionEl.dom.setAttribute('role', role);
40033         }
40034     },
40035
40036     /**
40037      * Sets the overflow on the content element of the component.
40038      * @param {Boolean} scroll True to allow the Component to auto scroll.
40039      * @return {Ext.Component} this
40040      */
40041     setAutoScroll : function(scroll){
40042         var me = this,
40043             targetEl;
40044         scroll = !!scroll;
40045         if (me.rendered) {
40046             targetEl = me.getTargetEl();
40047             targetEl.setStyle('overflow', scroll ? 'auto' : '');
40048             if (scroll && (Ext.isIE6 || Ext.isIE7)) {
40049                 // The scrollable container element must be non-statically positioned or IE6/7 will make
40050                 // positioned children stay in place rather than scrolling with the rest of the content
40051                 targetEl.position();
40052             }
40053         }
40054         me.autoScroll = scroll;
40055         return me;
40056     },
40057
40058     // private
40059     makeFloating : function(cfg){
40060         this.mixins.floating.constructor.call(this, cfg);
40061     },
40062
40063     initResizable: function(resizable) {
40064         resizable = Ext.apply({
40065             target: this,
40066             dynamic: false,
40067             constrainTo: this.constrainTo,
40068             handles: this.resizeHandles
40069         }, resizable);
40070         resizable.target = this;
40071         this.resizer = Ext.create('Ext.resizer.Resizer', resizable);
40072     },
40073
40074     getDragEl: function() {
40075         return this.el;
40076     },
40077
40078     initDraggable: function() {
40079         var me = this,
40080             ddConfig = Ext.applyIf({
40081                 el: this.getDragEl(),
40082                 constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.dom.parentNode)
40083             }, this.draggable);
40084
40085         // Add extra configs if Component is specified to be constrained
40086         if (me.constrain || me.constrainDelegate) {
40087             ddConfig.constrain = me.constrain;
40088             ddConfig.constrainDelegate = me.constrainDelegate;
40089         }
40090
40091         this.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
40092     },
40093
40094     /**
40095      * Sets the left and top of the component.  To set the page XY position instead, use {@link #setPagePosition}.
40096      * This method fires the {@link #move} event.
40097      * @param {Number} left The new left
40098      * @param {Number} top The new top
40099      * @param {Mixed} animate If true, the Component is <i>animated</i> into its new position. You may also pass an animation configuration.
40100      * @return {Ext.Component} this
40101      */
40102     setPosition: function(x, y, animate) {
40103         var me = this,
40104             el = me.el,
40105             to = {},
40106             adj, adjX, adjY, xIsNumber, yIsNumber;
40107
40108         if (Ext.isArray(x)) {
40109             animate = y;
40110             y = x[1];
40111             x = x[0];
40112         }
40113         me.x = x;
40114         me.y = y;
40115
40116         if (!me.rendered) {
40117             return me;
40118         }
40119
40120         adj = me.adjustPosition(x, y);
40121         adjX = adj.x;
40122         adjY = adj.y;
40123         xIsNumber = Ext.isNumber(adjX);
40124         yIsNumber = Ext.isNumber(adjY);
40125
40126         if (xIsNumber || yIsNumber) {
40127             if (animate) {
40128                 if (xIsNumber) {
40129                     to.left = adjX;
40130                 }
40131                 if (yIsNumber) {
40132                     to.top = adjY;
40133                 }
40134
40135                 me.stopAnimation();
40136                 me.animate(Ext.apply({
40137                     duration: 1000,
40138                     listeners: {
40139                         afteranimate: Ext.Function.bind(me.afterSetPosition, me, [adjX, adjY])
40140                     },
40141                     to: to
40142                 }, animate));
40143             }
40144             else {
40145                 if (!xIsNumber) {
40146                     el.setTop(adjY);
40147                 }
40148                 else if (!yIsNumber) {
40149                     el.setLeft(adjX);
40150                 }
40151                 else {
40152                     el.setLeftTop(adjX, adjY);
40153                 }
40154                 me.afterSetPosition(adjX, adjY);
40155             }
40156         }
40157         return me;
40158     },
40159
40160     /**
40161      * @private Template method called after a Component has been positioned.
40162      */
40163     afterSetPosition: function(ax, ay) {
40164         this.onPosition(ax, ay);
40165         this.fireEvent('move', this, ax, ay);
40166     },
40167
40168     showAt: function(x, y, animate) {
40169         // A floating Component is positioned relative to its ownerCt if any.
40170         if (this.floating) {
40171             this.setPosition(x, y, animate);
40172         } else {
40173             this.setPagePosition(x, y, animate);
40174         }
40175         this.show();
40176     },
40177
40178     /**
40179      * Sets the page XY position of the component.  To set the left and top instead, use {@link #setPosition}.
40180      * This method fires the {@link #move} event.
40181      * @param {Number} x The new x position
40182      * @param {Number} y The new y position
40183      * @param {Mixed} animate If passed, the Component is <i>animated</i> into its new position. If this parameter
40184      * is a number, it is used as the animation duration in milliseconds.
40185      * @return {Ext.Component} this
40186      */
40187     setPagePosition: function(x, y, animate) {
40188         var me = this,
40189             p;
40190
40191         if (Ext.isArray(x)) {
40192             y = x[1];
40193             x = x[0];
40194         }
40195         me.pageX = x;
40196         me.pageY = y;
40197         if (me.floating && me.floatParent) {
40198             // Floating Components being positioned in their ownerCt have to be made absolute
40199             p = me.floatParent.getTargetEl().getViewRegion();
40200             if (Ext.isNumber(x) && Ext.isNumber(p.left)) {
40201                 x -= p.left;
40202             }
40203             if (Ext.isNumber(y) && Ext.isNumber(p.top)) {
40204                 y -= p.top;
40205             }
40206             me.setPosition(x, y, animate);
40207         }
40208         else {
40209             p = me.el.translatePoints(x, y);
40210             me.setPosition(p.left, p.top, animate);
40211         }
40212         return me;
40213     },
40214
40215     /**
40216      * Gets the current box measurements of the component's underlying element.
40217      * @param {Boolean} local (optional) If true the element's left and top are returned instead of page XY (defaults to false)
40218      * @return {Object} box An object in the format {x, y, width, height}
40219      */
40220     getBox : function(local){
40221         var pos = this.getPosition(local);
40222         var s = this.getSize();
40223         s.x = pos[0];
40224         s.y = pos[1];
40225         return s;
40226     },
40227
40228     /**
40229      * Sets the current box measurements of the component's underlying element.
40230      * @param {Object} box An object in the format {x, y, width, height}
40231      * @return {Ext.Component} this
40232      */
40233     updateBox : function(box){
40234         this.setSize(box.width, box.height);
40235         this.setPagePosition(box.x, box.y);
40236         return this;
40237     },
40238
40239     // Include margins
40240     getOuterSize: function() {
40241         var el = this.el;
40242         return {
40243             width: el.getWidth() + el.getMargin('lr'),
40244             height: el.getHeight() + el.getMargin('tb')
40245         };
40246     },
40247
40248     // private
40249     adjustSize: function(w, h) {
40250         if (this.autoWidth) {
40251             w = 'auto';
40252         }
40253
40254         if (this.autoHeight) {
40255             h = 'auto';
40256         }
40257
40258         return {
40259             width: w,
40260             height: h
40261         };
40262     },
40263
40264     // private
40265     adjustPosition: function(x, y) {
40266
40267         // Floating Components being positioned in their ownerCt have to be made absolute
40268         if (this.floating && this.floatParent) {
40269             var o = this.floatParent.getTargetEl().getViewRegion();
40270             x += o.left;
40271             y += o.top;
40272         }
40273
40274         return {
40275             x: x,
40276             y: y
40277         };
40278     },
40279
40280     /**
40281      * Gets the current XY position of the component's underlying element.
40282      * @param {Boolean} local (optional) If true the element's left and top are returned instead of page XY (defaults to false)
40283      * @return {Array} The XY position of the element (e.g., [100, 200])
40284      */
40285     getPosition: function(local) {
40286         var el = this.el,
40287             xy;
40288
40289         if (local === true) {
40290             return [el.getLeft(true), el.getTop(true)];
40291         }
40292         xy = this.xy || el.getXY();
40293
40294         // Floating Components in an ownerCt have to have their positions made relative
40295         if (this.floating && this.floatParent) {
40296             var o = this.floatParent.getTargetEl().getViewRegion();
40297             xy[0] -= o.left;
40298             xy[1] -= o.top;
40299         }
40300         return xy;
40301     },
40302
40303     // Todo: add in xtype prefix support
40304     getId: function() {
40305         return this.id || (this.id = (this.getXType() || 'ext-comp') + '-' + this.getAutoId());
40306     },
40307
40308     onEnable: function() {
40309         var actionEl = this.getActionEl();
40310         actionEl.dom.removeAttribute('aria-disabled');
40311         actionEl.dom.disabled = false;
40312         this.callParent();
40313     },
40314
40315     onDisable: function() {
40316         var actionEl = this.getActionEl();
40317         actionEl.dom.setAttribute('aria-disabled', true);
40318         actionEl.dom.disabled = true;
40319         this.callParent();
40320     },
40321
40322     /**
40323      * <p>Shows this Component, rendering it first if {@link #autoRender} or {{@link "floating} are <code>true</code>.</p>
40324      * <p>After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and brought to the front of
40325      * its {@link #ZIndexManager z-index stack}.</p>
40326      * @param {String/Element} animateTarget Optional, and <b>only valid for {@link #floating} Components such as
40327      * {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
40328      * with <code>floating: true</code>.</b> The target from which the Component should
40329      * animate from while opening (defaults to null with no animation)
40330      * @param {Function} callback (optional) A callback function to call after the Component is displayed. Only necessary if animation was specified.
40331      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this Component.
40332      * @return {Component} this
40333      */
40334     show: function(animateTarget, cb, scope) {
40335         if (this.rendered && this.isVisible()) {
40336             if (this.toFrontOnShow && this.floating) {
40337                 this.toFront();
40338             }
40339         } else if (this.fireEvent('beforeshow', this) !== false) {
40340             this.hidden = false;
40341
40342             // Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
40343             if (!this.rendered && (this.autoRender || this.floating)) {
40344                 this.doAutoRender();
40345             }
40346             if (this.rendered) {
40347                 this.beforeShow();
40348                 this.onShow.apply(this, arguments);
40349
40350                 // Notify any owning Container unless it's suspended.
40351                 // Floating Components do not participate in layouts.
40352                 if (this.ownerCt && !this.floating && !(this.ownerCt.suspendLayout || this.ownerCt.layout.layoutBusy)) {
40353                     this.ownerCt.doLayout();
40354                 }
40355                 this.afterShow.apply(this, arguments);
40356             }
40357         }
40358         return this;
40359     },
40360
40361     beforeShow: Ext.emptyFn,
40362
40363     // Private. Override in subclasses where more complex behaviour is needed.
40364     onShow: function() {
40365         var me = this;
40366
40367         me.el.show();
40368         if (this.floating && this.constrain) {
40369             this.doConstrain();
40370         }
40371         me.callParent(arguments);
40372     },
40373
40374     afterShow: function(animateTarget, cb, scope) {
40375         var me = this,
40376             fromBox,
40377             toBox,
40378             ghostPanel;
40379
40380         // Default to configured animate target if none passed
40381         animateTarget = animateTarget || me.animateTarget;
40382
40383         // Need to be able to ghost the Component
40384         if (!me.ghost) {
40385             animateTarget = null;
40386         }
40387         // If we're animating, kick of an animation of the ghost from the target to the *Element* current box
40388         if (animateTarget) {
40389             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
40390             toBox = me.el.getBox();
40391             fromBox = animateTarget.getBox();
40392             fromBox.width += 'px';
40393             fromBox.height += 'px';
40394             toBox.width += 'px';
40395             toBox.height += 'px';
40396             me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
40397             ghostPanel = me.ghost();
40398             ghostPanel.el.stopAnimation();
40399
40400             ghostPanel.el.animate({
40401                 from: fromBox,
40402                 to: toBox,
40403                 listeners: {
40404                     afteranimate: function() {
40405                         delete ghostPanel.componentLayout.lastComponentSize;
40406                         me.unghost();
40407                         me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
40408                         if (me.floating) {
40409                             me.toFront();
40410                         }
40411                         Ext.callback(cb, scope || me);
40412                     }
40413                 }
40414             });
40415         }
40416         else {
40417             if (me.floating) {
40418                 me.toFront();
40419             }
40420             Ext.callback(cb, scope || me);
40421         }
40422         me.fireEvent('show', me);
40423     },
40424
40425     /**
40426      * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
40427      * @param {String/Element/Component} animateTarget Optional, and <b>only valid for {@link #floating} Components such as
40428      * {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
40429      * with <code>floating: true</code>.</b>.
40430      * The target to which the Component should animate while hiding (defaults to null with no animation)
40431      * @param {Function} callback (optional) A callback function to call after the Component is hidden.
40432      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this Component.
40433      * @return {Ext.Component} this
40434      */
40435     hide: function() {
40436
40437         // Clear the flag which is set if a floatParent was hidden while this is visible.
40438         // If a hide operation was subsequently called, that pending show must be hidden.
40439         this.showOnParentShow = false;
40440
40441         if (!(this.rendered && !this.isVisible()) && this.fireEvent('beforehide', this) !== false) {
40442             this.hidden = true;
40443             if (this.rendered) {
40444                 this.onHide.apply(this, arguments);
40445
40446                 // Notify any owning Container unless it's suspended.
40447                 // Floating Components do not participate in layouts.
40448                 if (this.ownerCt && !this.floating && !(this.ownerCt.suspendLayout || this.ownerCt.layout.layoutBusy)) {
40449                     this.ownerCt.doLayout();
40450                 }
40451             }
40452         }
40453         return this;
40454     },
40455
40456     // Possibly animate down to a target element.
40457     onHide: function(animateTarget, cb, scope) {
40458         var me = this,
40459             ghostPanel,
40460             toBox;
40461
40462         // Default to configured animate target if none passed
40463         animateTarget = animateTarget || me.animateTarget;
40464
40465         // Need to be able to ghost the Component
40466         if (!me.ghost) {
40467             animateTarget = null;
40468         }
40469         // If we're animating, kick off an animation of the ghost down to the target
40470         if (animateTarget) {
40471             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
40472             ghostPanel = me.ghost();
40473             ghostPanel.el.stopAnimation();
40474             toBox = animateTarget.getBox();
40475             toBox.width += 'px';
40476             toBox.height += 'px';
40477             ghostPanel.el.animate({
40478                 to: toBox,
40479                 listeners: {
40480                     afteranimate: function() {
40481                         delete ghostPanel.componentLayout.lastComponentSize;
40482                         ghostPanel.el.hide();
40483                         me.afterHide(cb, scope);
40484                     }
40485                 }
40486             });
40487         }
40488         me.el.hide();
40489         if (!animateTarget) {
40490             me.afterHide(cb, scope);
40491         }
40492     },
40493
40494     afterHide: function(cb, scope) {
40495         Ext.callback(cb, scope || this);
40496         this.fireEvent('hide', this);
40497     },
40498
40499     /**
40500      * @private
40501      * Template method to contribute functionality at destroy time.
40502      */
40503     onDestroy: function() {
40504         var me = this;
40505
40506         // Ensure that any ancillary components are destroyed.
40507         if (me.rendered) {
40508             Ext.destroy(
40509                 me.proxy,
40510                 me.resizer
40511             );
40512             // Different from AbstractComponent
40513             if (me.actionMode == 'container' || me.removeMode == 'container') {
40514                 me.container.remove();
40515             }
40516         }
40517         delete me.focusTask;
40518         me.callParent();
40519     },
40520
40521     deleteMembers: function() {
40522         var args = arguments,
40523             len = args.length,
40524             i = 0;
40525         for (; i < len; ++i) {
40526             delete this[args[i]];
40527         }
40528     },
40529
40530     /**
40531      * Try to focus this component.
40532      * @param {Boolean} selectText (optional) If applicable, true to also select the text in this component
40533      * @param {Boolean/Number} delay (optional) Delay the focus this number of milliseconds (true for 10 milliseconds).
40534      * @return {Ext.Component} this
40535      */
40536     focus: function(selectText, delay) {
40537         var me = this,
40538                 focusEl;
40539
40540         if (delay) {
40541             if (!me.focusTask) {
40542                 me.focusTask = Ext.create('Ext.util.DelayedTask', me.focus);
40543             }
40544             me.focusTask.delay(Ext.isNumber(delay) ? delay : 10, null, me, [selectText, false]);
40545             return me;
40546         }
40547
40548         if (me.rendered && !me.isDestroyed) {
40549             // getFocusEl could return a Component.
40550             focusEl = me.getFocusEl();
40551             focusEl.focus();
40552             if (focusEl.dom && selectText === true) {
40553                 focusEl.dom.select();
40554             }
40555
40556             // Focusing a floating Component brings it to the front of its stack.
40557             // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.
40558             if (me.floating) {
40559                 me.toFront(true);
40560             }
40561         }
40562         return me;
40563     },
40564
40565     /**
40566      * @private
40567      * Returns the focus holder element associated with this Component. By default, this is the Component's encapsulating
40568      * element. Subclasses which use embedded focusable elements (such as Window and Button) should override this for use
40569      * by the {@link #focus} method.
40570      * @returns {Ext.core.Element} the focus holing element.
40571      */
40572     getFocusEl: function() {
40573         return this.el;
40574     },
40575
40576     // private
40577     blur: function() {
40578         if (this.rendered) {
40579             this.getFocusEl().blur();
40580         }
40581         return this;
40582     },
40583
40584     getEl: function() {
40585         return this.el;
40586     },
40587
40588     // Deprecate 5.0
40589     getResizeEl: function() {
40590         return this.el;
40591     },
40592
40593     // Deprecate 5.0
40594     getPositionEl: function() {
40595         return this.el;
40596     },
40597
40598     // Deprecate 5.0
40599     getActionEl: function() {
40600         return this.el;
40601     },
40602
40603     // Deprecate 5.0
40604     getVisibilityEl: function() {
40605         return this.el;
40606     },
40607
40608     // Deprecate 5.0
40609     onResize: Ext.emptyFn,
40610
40611     // private
40612     getBubbleTarget: function() {
40613         return this.ownerCt;
40614     },
40615
40616     // private
40617     getContentTarget: function() {
40618         return this.el;
40619     },
40620
40621     /**
40622      * Clone the current component using the original config values passed into this instance by default.
40623      * @param {Object} overrides A new config containing any properties to override in the cloned version.
40624      * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
40625      * @return {Ext.Component} clone The cloned copy of this component
40626      */
40627     cloneConfig: function(overrides) {
40628         overrides = overrides || {};
40629         var id = overrides.id || Ext.id();
40630         var cfg = Ext.applyIf(overrides, this.initialConfig);
40631         cfg.id = id;
40632
40633         var self = Ext.getClass(this);
40634
40635         // prevent dup id
40636         return new self(cfg);
40637     },
40638
40639     /**
40640      * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all
40641      * available xtypes, see the {@link Ext.Component} header. Example usage:
40642      * <pre><code>
40643 var t = new Ext.form.field.Text();
40644 alert(t.getXType());  // alerts 'textfield'
40645 </code></pre>
40646      * @return {String} The xtype
40647      */
40648     getXType: function() {
40649         return this.self.xtype;
40650     },
40651
40652     /**
40653      * Find a container above this component at any level by a custom function. If the passed function returns
40654      * true, the container will be returned.
40655      * @param {Function} fn The custom function to call with the arguments (container, this component).
40656      * @return {Ext.container.Container} The first Container for which the custom function returns true
40657      */
40658     findParentBy: function(fn) {
40659         var p;
40660
40661         // Iterate up the ownerCt chain until there's no ownerCt, or we find an ancestor which matches using the selector function.
40662         for (p = this.ownerCt; p && !fn(p, this); p = p.ownerCt);
40663         return p || null;
40664     },
40665
40666     /**
40667      * <p>Find a container above this component at any level by xtype or class</p>
40668      * <p>See also the {@link Ext.Component#up up} method.</p>
40669      * @param {String/Class} xtype The xtype string for a component, or the class of the component directly
40670      * @return {Ext.container.Container} The first Container which matches the given xtype or class
40671      */
40672     findParentByType: function(xtype) {
40673         return Ext.isFunction(xtype) ?
40674             this.findParentBy(function(p) {
40675                 return p.constructor === xtype;
40676             })
40677         :
40678             this.up(xtype);
40679     },
40680
40681     /**
40682      * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (<i>this</i>) of
40683      * function call will be the scope provided or the current component. The arguments to the function
40684      * will be the args provided or the current component. If the function returns false at any point,
40685      * the bubble is stopped.
40686      * @param {Function} fn The function to call
40687      * @param {Object} scope (optional) The scope of the function (defaults to current node)
40688      * @param {Array} args (optional) The args to call the function with (default to passing the current component)
40689      * @return {Ext.Component} this
40690      */
40691     bubble: function(fn, scope, args) {
40692         var p = this;
40693         while (p) {
40694             if (fn.apply(scope || p, args || [p]) === false) {
40695                 break;
40696             }
40697             p = p.ownerCt;
40698         }
40699         return this;
40700     },
40701
40702     getProxy: function() {
40703         if (!this.proxy) {
40704             this.proxy = this.el.createProxy(Ext.baseCSSPrefix + 'proxy-el', Ext.getBody(), true);
40705         }
40706         return this.proxy;
40707     }
40708
40709 });
40710
40711 /**
40712 * @class Ext.layout.container.Container
40713 * @extends Ext.layout.container.AbstractContainer
40714 * @private
40715 * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.container.Container#layout layout}</b></tt>
40716 * configuration property.  See <tt><b>{@link Ext.container.Container#layout}</b></tt> for additional details.</p>
40717 */
40718 Ext.define('Ext.layout.container.Container', {
40719
40720     /* Begin Definitions */
40721
40722     extend: 'Ext.layout.container.AbstractContainer',
40723     alternateClassName: 'Ext.layout.ContainerLayout',
40724     
40725     /* End Definitions */
40726
40727     layoutItem: function(item, box) {
40728         box = box || {};
40729         if (item.componentLayout.initialized !== true) {
40730             this.setItemSize(item, box.width || item.width || undefined, box.height || item.height || undefined);
40731             // item.doComponentLayout(box.width || item.width || undefined, box.height || item.height || undefined);
40732         }
40733     },
40734
40735     getLayoutTargetSize : function() {
40736         var target = this.getTarget(),
40737             ret;
40738
40739         if (target) {
40740             ret = target.getViewSize();
40741
40742             // IE in will sometimes return a width of 0 on the 1st pass of getViewSize.
40743             // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
40744             // with getViewSize
40745             if (Ext.isIE && ret.width == 0){
40746                 ret = target.getStyleSize();
40747             }
40748
40749             ret.width -= target.getPadding('lr');
40750             ret.height -= target.getPadding('tb');
40751         }
40752         return ret;
40753     },
40754
40755     beforeLayout: function() {
40756         if (this.owner.beforeLayout(arguments) !== false) {
40757             return this.callParent(arguments);
40758         }
40759         else {
40760             return false;
40761         }
40762     },
40763
40764     afterLayout: function() {
40765         this.owner.afterLayout(arguments);
40766         this.callParent(arguments);
40767     },
40768
40769     /**
40770      * @protected
40771      * Returns all items that are rendered
40772      * @return {Array} All matching items
40773      */
40774     getRenderedItems: function() {
40775         var me = this,
40776             target = me.getTarget(),
40777             items = me.getLayoutItems(),
40778             ln = items.length,
40779             renderedItems = [],
40780             i, item;
40781
40782         for (i = 0; i < ln; i++) {
40783             item = items[i];
40784             if (item.rendered && me.isValidParent(item, target, i)) {
40785                 renderedItems.push(item);
40786             }
40787         }
40788
40789         return renderedItems;
40790     },
40791
40792     /**
40793      * @protected
40794      * Returns all items that are both rendered and visible
40795      * @return {Array} All matching items
40796      */
40797     getVisibleItems: function() {
40798         var target   = this.getTarget(),
40799             items = this.getLayoutItems(),
40800             ln = items.length,
40801             visibleItems = [],
40802             i, item;
40803
40804         for (i = 0; i < ln; i++) {
40805             item = items[i];
40806             if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) {
40807                 visibleItems.push(item);
40808             }
40809         }
40810
40811         return visibleItems;
40812     }
40813 });
40814 /**
40815  * @class Ext.layout.container.Auto
40816  * @extends Ext.layout.container.Container
40817  *
40818  * <p>The AutoLayout is the default layout manager delegated by {@link Ext.container.Container} to
40819  * render any child Components when no <tt>{@link Ext.container.Container#layout layout}</tt> is configured into
40820  * a <tt>{@link Ext.container.Container Container}.</tt>.  AutoLayout provides only a passthrough of any layout calls
40821  * to any child containers.</p>
40822  * {@img Ext.layout.container.Auto/Ext.layout.container.Auto.png Ext.layout.container.Auto container layout}
40823  * Example usage:
40824         Ext.create('Ext.Panel', {
40825                 width: 500,
40826                 height: 280,
40827                 title: "AutoLayout Panel",
40828                 layout: 'auto',
40829                 renderTo: document.body,
40830                 items: [{
40831                         xtype: 'panel',
40832                         title: 'Top Inner Panel',
40833                         width: '75%',
40834                         height: 90
40835                 },{
40836                         xtype: 'panel',
40837                         title: 'Bottom Inner Panel',
40838                         width: '75%',
40839                         height: 90
40840                 }]
40841         });
40842  */
40843
40844 Ext.define('Ext.layout.container.Auto', {
40845
40846     /* Begin Definitions */
40847
40848     alias: ['layout.auto', 'layout.autocontainer'],
40849
40850     extend: 'Ext.layout.container.Container',
40851
40852     /* End Definitions */
40853
40854     type: 'autocontainer',
40855
40856     fixedLayout: false,
40857
40858     bindToOwnerCtComponent: true,
40859
40860     // @private
40861     onLayout : function(owner, target) {
40862         var me = this,
40863             items = me.getLayoutItems(),
40864             ln = items.length,
40865             i;
40866
40867         // Ensure the Container is only primed with the clear element if there are child items.
40868         if (ln) {
40869             // Auto layout uses natural HTML flow to arrange the child items.
40870             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
40871             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
40872             if (!me.clearEl) {
40873                 me.clearEl = me.getRenderTarget().createChild({
40874                     cls: Ext.baseCSSPrefix + 'clear',
40875                     role: 'presentation'
40876                 });
40877             }
40878
40879             // Auto layout allows CSS to size its child items.
40880             for (i = 0; i < ln; i++) {
40881                 me.setItemSize(items[i]);
40882             }
40883         }
40884     }
40885 });
40886 /**
40887  * @class Ext.container.AbstractContainer
40888  * @extends Ext.Component
40889  * <p>An abstract base class which provides shared methods for Containers across the Sencha product line.</p>
40890  * Please refer to sub class's documentation
40891  */
40892 Ext.define('Ext.container.AbstractContainer', {
40893
40894     /* Begin Definitions */
40895
40896     extend: 'Ext.Component',
40897
40898     requires: [
40899         'Ext.util.MixedCollection',
40900         'Ext.layout.container.Auto',
40901         'Ext.ZIndexManager'
40902     ],
40903
40904     /* End Definitions */
40905     /**
40906      * @cfg {String/Object} layout
40907      * <p><b>*Important</b>: In order for child items to be correctly sized and
40908      * positioned, typically a layout manager <b>must</b> be specified through
40909      * the <code>layout</code> configuration option.</p>
40910      * <br><p>The sizing and positioning of child {@link #items} is the responsibility of
40911      * the Container's layout manager which creates and manages the type of layout
40912      * you have in mind.  For example:</p>
40913      * <p>If the {@link #layout} configuration is not explicitly specified for
40914      * a general purpose container (e.g. Container or Panel) the
40915      * {@link Ext.layout.container.Auto default layout manager} will be used
40916      * which does nothing but render child components sequentially into the
40917      * Container (no sizing or positioning will be performed in this situation).</p>
40918      * <br><p><b><code>layout</code></b> may be specified as either as an Object or
40919      * as a String:</p><div><ul class="mdetail-params">
40920      *
40921      * <li><u>Specify as an Object</u></li>
40922      * <div><ul class="mdetail-params">
40923      * <li>Example usage:</li>
40924      * <pre><code>
40925 layout: {
40926     type: 'vbox',
40927     align: 'left'
40928 }
40929        </code></pre>
40930      *
40931      * <li><code><b>type</b></code></li>
40932      * <br/><p>The layout type to be used for this container.  If not specified,
40933      * a default {@link Ext.layout.container.Auto} will be created and used.</p>
40934      * <br/><p>Valid layout <code>type</code> values are:</p>
40935      * <div class="sub-desc"><ul class="mdetail-params">
40936      * <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> &nbsp;&nbsp;&nbsp; <b>Default</b></li>
40937      * <li><code><b>{@link Ext.layout.container.Card card}</b></code></li>
40938      * <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li>
40939      * <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li>
40940      * <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li>
40941      * <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li>
40942      * <li><code><b>{@link Ext.layout.container.Table table}</b></code></li>
40943      * </ul></div>
40944      *
40945      * <li>Layout specific configuration properties</li>
40946      * <br/><p>Additional layout specific configuration properties may also be
40947      * specified. For complete details regarding the valid config options for
40948      * each layout type, see the layout class corresponding to the <code>type</code>
40949      * specified.</p>
40950      *
40951      * </ul></div>
40952      *
40953      * <li><u>Specify as a String</u></li>
40954      * <div><ul class="mdetail-params">
40955      * <li>Example usage:</li>
40956      * <pre><code>
40957 layout: {
40958     type: 'vbox',
40959     padding: '5',
40960     align: 'left'
40961 }
40962        </code></pre>
40963      * <li><code><b>layout</b></code></li>
40964      * <br/><p>The layout <code>type</code> to be used for this container (see list
40965      * of valid layout type values above).</p><br/>
40966      * <br/><p>Additional layout specific configuration properties. For complete
40967      * details regarding the valid config options for each layout type, see the
40968      * layout class corresponding to the <code>layout</code> specified.</p>
40969      * </ul></div></ul></div>
40970      */
40971
40972     /**
40973      * @cfg {String/Number} activeItem
40974      * A string component id or the numeric index of the component that should be initially activated within the
40975      * container's layout on render.  For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
40976      * item in the container's collection).  activeItem only applies to layout styles that can display
40977      * items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}).
40978      */
40979     /**
40980      * @cfg {Object/Array} items
40981      * <p>A single item, or an array of child Components to be added to this container</p>
40982      * <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into
40983      * its encapsulating element and performs no sizing or positioning upon them.</b><p>
40984      * <p>Example:</p>
40985      * <pre><code>
40986 // specifying a single item
40987 items: {...},
40988 layout: 'fit',    // The single items is sized to fit
40989
40990 // specifying multiple items
40991 items: [{...}, {...}],
40992 layout: 'hbox', // The items are arranged horizontally
40993        </code></pre>
40994      * <p>Each item may be:</p>
40995      * <ul>
40996      * <li>A {@link Ext.Component Component}</li>
40997      * <li>A Component configuration object</li>
40998      * </ul>
40999      * <p>If a configuration object is specified, the actual type of Component to be
41000      * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p>
41001      * <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p>
41002      * <p>If an {@link Ext.Component#xtype xtype} is not explicitly
41003      * specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p>
41004      * <p><b>Notes</b>:</p>
41005      * <p>Ext uses lazy rendering. Child Components will only be rendered
41006      * should it become necessary. Items are automatically laid out when they are first
41007      * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p>
41008      * <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or 
41009      * <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p>
41010      */
41011     /**
41012      * @cfg {Object|Function} defaults
41013      * <p>This option is a means of applying default settings to all added items whether added through the {@link #items}
41014      * config or via the {@link #add} or {@link #insert} methods.</p>
41015      * <p>If an added item is a config object, and <b>not</b> an instantiated Component, then the default properties are
41016      * unconditionally applied. If the added item <b>is</b> an instantiated Component, then the default properties are
41017      * applied conditionally so as not to override existing properties in the item.</p>
41018      * <p>If the defaults option is specified as a function, then the function will be called using this Container as the
41019      * scope (<code>this</code> reference) and passing the added item as the first parameter. Any resulting object
41020      * from that call is then applied to the item as default properties.</p>
41021      * <p>For example, to automatically apply padding to the body of each of a set of
41022      * contained {@link Ext.panel.Panel} items, you could pass: <code>defaults: {bodyStyle:'padding:15px'}</code>.</p>
41023      * <p>Usage:</p><pre><code>
41024 defaults: {               // defaults are applied to items, not the container
41025     autoScroll:true
41026 },
41027 items: [
41028     {
41029         xtype: 'panel',   // defaults <b>do not</b> have precedence over
41030         id: 'panel1',     // options in config objects, so the defaults
41031         autoScroll: false // will not be applied here, panel1 will be autoScroll:false
41032     },
41033     new Ext.panel.Panel({       // defaults <b>do</b> have precedence over options
41034         id: 'panel2',     // options in components, so the defaults
41035         autoScroll: false // will be applied here, panel2 will be autoScroll:true.
41036     })
41037 ]</code></pre>
41038      */
41039
41040     /** @cfg {Boolean} suspendLayout
41041      * If true, suspend calls to doLayout.  Useful when batching multiple adds to a container and not passing them
41042      * as multiple arguments or an array.
41043      */
41044     suspendLayout : false,
41045
41046     /** @cfg {Boolean} autoDestroy
41047      * If true the container will automatically destroy any contained component that is removed from it, else
41048      * destruction must be handled manually.
41049      * Defaults to true.
41050      */
41051     autoDestroy : true,
41052
41053      /** @cfg {String} defaultType
41054       * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
41055       * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
41056       * <p>Defaults to <code>'panel'</code>.</p>
41057       */
41058     defaultType: 'panel',
41059
41060     isContainer : true,
41061
41062     baseCls: Ext.baseCSSPrefix + 'container',
41063
41064     /**
41065      * @cfg {Array} bubbleEvents
41066      * <p>An array of events that, when fired, should be bubbled to any parent container.
41067      * See {@link Ext.util.Observable#enableBubble}.
41068      * Defaults to <code>['add', 'remove']</code>.
41069      */
41070     bubbleEvents: ['add', 'remove'],
41071     
41072     // @private
41073     initComponent : function(){
41074         var me = this;
41075         me.addEvents(
41076             /**
41077              * @event afterlayout
41078              * Fires when the components in this container are arranged by the associated layout manager.
41079              * @param {Ext.container.Container} this
41080              * @param {ContainerLayout} layout The ContainerLayout implementation for this container
41081              */
41082             'afterlayout',
41083             /**
41084              * @event beforeadd
41085              * Fires before any {@link Ext.Component} is added or inserted into the container.
41086              * A handler can return false to cancel the add.
41087              * @param {Ext.container.Container} this
41088              * @param {Ext.Component} component The component being added
41089              * @param {Number} index The index at which the component will be added to the container's items collection
41090              */
41091             'beforeadd',
41092             /**
41093              * @event beforeremove
41094              * Fires before any {@link Ext.Component} is removed from the container.  A handler can return
41095              * false to cancel the remove.
41096              * @param {Ext.container.Container} this
41097              * @param {Ext.Component} component The component being removed
41098              */
41099             'beforeremove',
41100             /**
41101              * @event add
41102              * @bubbles
41103              * Fires after any {@link Ext.Component} is added or inserted into the container.
41104              * @param {Ext.container.Container} this
41105              * @param {Ext.Component} component The component that was added
41106              * @param {Number} index The index at which the component was added to the container's items collection
41107              */
41108             'add',
41109             /**
41110              * @event remove
41111              * @bubbles
41112              * Fires after any {@link Ext.Component} is removed from the container.
41113              * @param {Ext.container.Container} this
41114              * @param {Ext.Component} component The component that was removed
41115              */
41116             'remove',
41117             /**
41118              * @event beforecardswitch
41119              * Fires before this container switches the active card. This event
41120              * is only available if this container uses a CardLayout. Note that
41121              * TabPanel and Carousel both get a CardLayout by default, so both
41122              * will have this event.
41123              * A handler can return false to cancel the card switch.
41124              * @param {Ext.container.Container} this
41125              * @param {Ext.Component} newCard The card that will be switched to
41126              * @param {Ext.Component} oldCard The card that will be switched from
41127              * @param {Number} index The index of the card that will be switched to
41128              * @param {Boolean} animated True if this cardswitch will be animated
41129              */
41130             'beforecardswitch',
41131             /**
41132              * @event cardswitch
41133              * Fires after this container switches the active card. If the card
41134              * is switched using an animation, this event will fire after the
41135              * animation has finished. This event is only available if this container
41136              * uses a CardLayout. Note that TabPanel and Carousel both get a CardLayout
41137              * by default, so both will have this event.
41138              * @param {Ext.container.Container} this
41139              * @param {Ext.Component} newCard The card that has been switched to
41140              * @param {Ext.Component} oldCard The card that has been switched from
41141              * @param {Number} index The index of the card that has been switched to
41142              * @param {Boolean} animated True if this cardswitch was animated
41143              */
41144             'cardswitch'
41145         );
41146
41147         // layoutOnShow stack
41148         me.layoutOnShow = Ext.create('Ext.util.MixedCollection');
41149         me.callParent();
41150         me.initItems();
41151     },
41152
41153     // @private
41154     initItems : function() {
41155         var me = this,
41156             items = me.items;
41157
41158         /**
41159          * The MixedCollection containing all the child items of this container.
41160          * @property items
41161          * @type Ext.util.MixedCollection
41162          */
41163         me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
41164
41165         if (items) {
41166             if (!Ext.isArray(items)) {
41167                 items = [items];
41168             }
41169
41170             me.add(items);
41171         }
41172     },
41173
41174     // @private
41175     afterRender : function() {
41176         this.getLayout();
41177         this.callParent();
41178     },
41179
41180     // @private
41181     setLayout : function(layout) {
41182         var currentLayout = this.layout;
41183
41184         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
41185             currentLayout.setOwner(null);
41186         }
41187
41188         this.layout = layout;
41189         layout.setOwner(this);
41190     },
41191
41192     /**
41193      * Returns the {@link Ext.layout.container.AbstractContainer layout} instance currently associated with this Container.
41194      * If a layout has not been instantiated yet, that is done first
41195      * @return {Ext.layout.container.AbstractContainer} The layout
41196      */
41197     getLayout : function() {
41198         var me = this;
41199         if (!me.layout || !me.layout.isLayout) {
41200             me.setLayout(Ext.layout.Layout.create(me.layout, 'autocontainer'));
41201         }
41202
41203         return me.layout;
41204     },
41205
41206     /**
41207      * Manually force this container's layout to be recalculated.  The framwork uses this internally to refresh layouts
41208      * form most cases.
41209      * @return {Ext.container.Container} this
41210      */
41211     doLayout : function() {
41212         var me = this,
41213             layout = me.getLayout();
41214
41215         if (me.rendered && layout && !me.suspendLayout) {
41216             // If either dimension is being auto-set, then it requires a ComponentLayout to be run.
41217             if ((!Ext.isNumber(me.width) || !Ext.isNumber(me.height)) && me.componentLayout.type !== 'autocomponent') {
41218                 // Only run the ComponentLayout if it is not already in progress
41219                 if (me.componentLayout.layoutBusy !== true) {
41220                     me.doComponentLayout();
41221                     if (me.componentLayout.layoutCancelled === true) {
41222                         layout.layout();
41223                     }
41224                 }
41225             }
41226             // Both dimensions defined, run a ContainerLayout
41227             else {
41228                 // Only run the ContainerLayout if it is not already in progress
41229                 if (layout.layoutBusy !== true) {
41230                     layout.layout();
41231                 }
41232             }
41233         }
41234
41235         return me;
41236     },
41237
41238     // @private
41239     afterLayout : function(layout) {
41240         this.fireEvent('afterlayout', this, layout);
41241     },
41242
41243     // @private
41244     prepareItems : function(items, applyDefaults) {
41245         if (!Ext.isArray(items)) {
41246             items = [items];
41247         }
41248
41249         // Make sure defaults are applied and item is initialized
41250         var i = 0,
41251             len = items.length,
41252             item;
41253
41254         for (; i < len; i++) {
41255             item = items[i];
41256             if (applyDefaults) {
41257                 item = this.applyDefaults(item);
41258             }
41259             items[i] = this.lookupComponent(item);
41260         }
41261         return items;
41262     },
41263
41264     // @private
41265     applyDefaults : function(config) {
41266         var defaults = this.defaults;
41267
41268         if (defaults) {
41269             if (Ext.isFunction(defaults)) {
41270                 defaults = defaults.call(this, config);
41271             }
41272
41273             if (Ext.isString(config)) {
41274                 config = Ext.ComponentManager.get(config);
41275                 Ext.applyIf(config, defaults);
41276             } else if (!config.isComponent) {
41277                 Ext.applyIf(config, defaults);
41278             } else {
41279                 Ext.applyIf(config, defaults);
41280             }
41281         }
41282
41283         return config;
41284     },
41285
41286     // @private
41287     lookupComponent : function(comp) {
41288         return Ext.isString(comp) ? Ext.ComponentManager.get(comp) : this.createComponent(comp);
41289     },
41290
41291     // @private
41292     createComponent : function(config, defaultType) {
41293         // // add in ownerCt at creation time but then immediately
41294         // // remove so that onBeforeAdd can handle it
41295         // var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType);
41296         //
41297         // delete component.initialConfig.ownerCt;
41298         // delete component.ownerCt;
41299
41300         return Ext.ComponentManager.create(config, defaultType || this.defaultType);
41301     },
41302
41303     // @private - used as the key lookup function for the items collection
41304     getComponentId : function(comp) {
41305         return comp.getItemId();
41306     },
41307
41308     /**
41309
41310 Adds {@link Ext.Component Component}(s) to this Container.
41311
41312 ##Description:##
41313
41314 - Fires the {@link #beforeadd} event before adding.
41315 - The Container's {@link #defaults default config values} will be applied
41316   accordingly (see `{@link #defaults}` for details).
41317 - Fires the `{@link #add}` event after the component has been added.
41318
41319 ##Notes:##
41320
41321 If the Container is __already rendered__ when `add`
41322 is called, it will render the newly added Component into its content area.
41323
41324 __**If**__ the Container was configured with a size-managing {@link #layout} manager, the Container
41325 will recalculate its internal layout at this time too.
41326
41327 Note that the default layout manager simply renders child Components sequentially into the content area and thereafter performs no sizing.
41328
41329 If adding multiple new child Components, pass them as an array to the `add` method, so that only one layout recalculation is performed.
41330
41331     tb = new {@link Ext.toolbar.Toolbar}({
41332         renderTo: document.body
41333     });  // toolbar is rendered
41334     tb.add([{text:'Button 1'}, {text:'Button 2'}]); // add multiple items. ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
41335
41336 ##Warning:## 
41337
41338 Components directly managed by the BorderLayout layout manager
41339 may not be removed or added.  See the Notes for {@link Ext.layout.container.Border BorderLayout}
41340 for more details.
41341
41342      * @param {...Object/Array} Component
41343      * Either one or more Components to add or an Array of Components to add.
41344      * See `{@link #items}` for additional information.
41345      *
41346      * @return {Ext.Component/Array} The Components that were added.
41347      * @markdown
41348      */
41349     add : function() {
41350         var me = this,
41351             args = Array.prototype.slice.call(arguments),
41352             hasMultipleArgs,
41353             items,
41354             results = [],
41355             i,
41356             ln,
41357             item,
41358             index = -1,
41359             cmp;
41360
41361         if (typeof args[0] == 'number') {
41362             index = args.shift();
41363         }
41364
41365         hasMultipleArgs = args.length > 1;
41366         if (hasMultipleArgs || Ext.isArray(args[0])) {
41367
41368             items = hasMultipleArgs ? args : args[0];
41369             // Suspend Layouts while we add multiple items to the container
41370             me.suspendLayout = true;
41371             for (i = 0, ln = items.length; i < ln; i++) {
41372                 item = items[i];
41373                 
41374                 if (!item) {
41375                     Ext.Error.raise("Trying to add a null item as a child of Container with itemId/id: " + me.getItemId());
41376                 }
41377                 
41378                 if (index != -1) {
41379                     item = me.add(index + i, item);
41380                 } else {
41381                     item = me.add(item);
41382                 }
41383                 results.push(item);
41384             }
41385             // Resume Layouts now that all items have been added and do a single layout for all the items just added
41386             me.suspendLayout = false;
41387             me.doLayout();
41388             return results;
41389         }
41390
41391         cmp = me.prepareItems(args[0], true)[0];
41392
41393         // Floating Components are not added into the items collection
41394         // But they do get an upward ownerCt link so that they can traverse
41395         // up to their z-index parent.
41396         if (cmp.floating) {
41397             cmp.onAdded(me, index);
41398         } else {
41399             index = (index !== -1) ? index : me.items.length;
41400             if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) {
41401                 me.items.insert(index, cmp);
41402                 cmp.onAdded(me, index);
41403                 me.onAdd(cmp, index);
41404                 me.fireEvent('add', me, cmp, index);
41405             }
41406             me.doLayout();
41407         }
41408         return cmp;
41409     },
41410
41411     /**
41412      * @private
41413      * <p>Called by Component#doAutoRender</p>
41414      * <p>Register a Container configured <code>floating: true</code> with this Container's {@link Ext.ZIndexManager ZIndexManager}.</p>
41415      * <p>Components added in ths way will not participate in the layout, but will be rendered
41416      * upon first show in the way that {@link Ext.window.Window Window}s are.</p>
41417      * <p></p>
41418      */
41419     registerFloatingItem: function(cmp) {
41420         var me = this;
41421         if (!me.floatingItems) {
41422             me.floatingItems = Ext.create('Ext.ZIndexManager', me);
41423         }
41424         me.floatingItems.register(cmp);
41425     },
41426
41427     onAdd : Ext.emptyFn,
41428     onRemove : Ext.emptyFn,
41429
41430     /**
41431      * Inserts a Component into this Container at a specified index. Fires the
41432      * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
41433      * Component has been inserted.
41434      * @param {Number} index The index at which the Component will be inserted
41435      * into the Container's items collection
41436      * @param {Ext.Component} component The child Component to insert.<br><br>
41437      * Ext uses lazy rendering, and will only render the inserted Component should
41438      * it become necessary.<br><br>
41439      * A Component config object may be passed in order to avoid the overhead of
41440      * constructing a real Component object if lazy rendering might mean that the
41441      * inserted Component will not be rendered immediately. To take advantage of
41442      * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
41443      * property to the registered type of the Component wanted.<br><br>
41444      * For a list of all available xtypes, see {@link Ext.Component}.
41445      * @return {Ext.Component} component The Component (or config object) that was
41446      * inserted with the Container's default config values applied.
41447      */
41448     insert : function(index, comp) {
41449         return this.add(index, comp);
41450     },
41451
41452     /**
41453      * Moves a Component within the Container
41454      * @param {Number} fromIdx The index the Component you wish to move is currently at.
41455      * @param {Number} toIdx The new index for the Component.
41456      * @return {Ext.Component} component The Component (or config object) that was moved.
41457      */
41458     move : function(fromIdx, toIdx) {
41459         var items = this.items,
41460             item;
41461         item = items.removeAt(fromIdx);
41462         if (item === false) {
41463             return false;
41464         }
41465         items.insert(toIdx, item);
41466         this.doLayout();
41467         return item;
41468     },
41469
41470     // @private
41471     onBeforeAdd : function(item) {
41472         var me = this;
41473         
41474         if (item.ownerCt) {
41475             item.ownerCt.remove(item, false);
41476         }
41477
41478         if (me.border === false || me.border === 0) {
41479             item.border = (item.border === true);
41480         }
41481     },
41482
41483     /**
41484      * Removes a component from this container.  Fires the {@link #beforeremove} event before removing, then fires
41485      * the {@link #remove} event after the component has been removed.
41486      * @param {Component/String} component The component reference or id to remove.
41487      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
41488      * Defaults to the value of this Container's {@link #autoDestroy} config.
41489      * @return {Ext.Component} component The Component that was removed.
41490      */
41491     remove : function(comp, autoDestroy) {
41492         var me = this,
41493             c = me.getComponent(comp);
41494             if (Ext.isDefined(Ext.global.console) && !c) {
41495                 console.warn("Attempted to remove a component that does not exist. Ext.container.Container: remove takes an argument of the component to remove. cmp.remove() is incorrect usage.");
41496             }
41497
41498         if (c && me.fireEvent('beforeremove', me, c) !== false) {
41499             me.doRemove(c, autoDestroy);
41500             me.fireEvent('remove', me, c);
41501         }
41502
41503         return c;
41504     },
41505
41506     // @private
41507     doRemove : function(component, autoDestroy) {
41508         var me = this,
41509             layout = me.layout,
41510             hasLayout = layout && me.rendered;
41511
41512         me.items.remove(component);
41513         component.onRemoved();
41514
41515         if (hasLayout) {
41516             layout.onRemove(component);
41517         }
41518
41519         me.onRemove(component, autoDestroy);
41520
41521         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
41522             component.destroy();
41523         }
41524
41525         if (hasLayout && !autoDestroy) {
41526             layout.afterRemove(component);
41527         }
41528
41529         if (!me.destroying) {
41530             me.doLayout();
41531         }
41532     },
41533
41534     /**
41535      * Removes all components from this container.
41536      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
41537      * Defaults to the value of this Container's {@link #autoDestroy} config.
41538      * @return {Array} Array of the destroyed components
41539      */
41540     removeAll : function(autoDestroy) {
41541         var me = this,
41542             removeItems = me.items.items.slice(),
41543             items = [],
41544             i = 0,
41545             len = removeItems.length,
41546             item;
41547
41548         // Suspend Layouts while we remove multiple items from the container
41549         me.suspendLayout = true;
41550         for (; i < len; i++) {
41551             item = removeItems[i];
41552             me.remove(item, autoDestroy);
41553
41554             if (item.ownerCt !== me) {
41555                 items.push(item);
41556             }
41557         }
41558
41559         // Resume Layouts now that all items have been removed and do a single layout
41560         me.suspendLayout = false;
41561         me.doLayout();
41562         return items;
41563     },
41564
41565     // Used by ComponentQuery to retrieve all of the items
41566     // which can potentially be considered a child of this Container.
41567     // This should be overriden by components which have child items
41568     // that are not contained in items. For example dockedItems, menu, etc
41569     // IMPORTANT note for maintainers:
41570     //  Items are returned in tree traversal order. Each item is appended to the result array
41571     //  followed by the results of that child's getRefItems call.
41572     //  Floating child items are appended after internal child items.
41573     getRefItems : function(deep) {
41574         var me = this,
41575             items = me.items.items,
41576             len = items.length,
41577             i = 0,
41578             item,
41579             result = [];
41580
41581         for (; i < len; i++) {
41582             item = items[i];
41583             result.push(item);
41584             if (deep && item.getRefItems) {
41585                 result.push.apply(result, item.getRefItems(true));
41586             }
41587         }
41588
41589         // Append floating items to the list.
41590         // These will only be present after they are rendered.
41591         if (me.floatingItems && me.floatingItems.accessList) {
41592             result.push.apply(result, me.floatingItems.accessList);
41593         }
41594
41595         return result;
41596     },
41597
41598     /**
41599      * Cascades down the component/container heirarchy from this component (passed in the first call), calling the specified function with
41600      * each component. The scope (<code>this</code> reference) of the
41601      * function call will be the scope provided or the current component. The arguments to the function
41602      * will be the args provided or the current component. If the function returns false at any point,
41603      * the cascade is stopped on that branch.
41604      * @param {Function} fn The function to call
41605      * @param {Object} scope (optional) The scope of the function (defaults to current component)
41606      * @param {Array} args (optional) The args to call the function with. The current component always passed as the last argument.
41607      * @return {Ext.Container} this
41608      */
41609     cascade : function(fn, scope, origArgs){
41610         var me = this,
41611             cs = me.items ? me.items.items : [],
41612             len = cs.length,
41613             i = 0,
41614             c,
41615             args = origArgs ? origArgs.concat(me) : [me],
41616             componentIndex = args.length - 1;
41617
41618         if (fn.apply(scope || me, args) !== false) {
41619             for(; i < len; i++){
41620                 c = cs[i];
41621                 if (c.cascade) {
41622                     c.cascade(fn, scope, origArgs);
41623                 } else {
41624                     args[componentIndex] = c;
41625                     fn.apply(scope || cs, args);
41626                 }
41627             }
41628         }
41629         return this;
41630     },
41631
41632     /**
41633      * Examines this container's <code>{@link #items}</code> <b>property</b>
41634      * and gets a direct child component of this container.
41635      * @param {String/Number} comp This parameter may be any of the following:
41636      * <div><ul class="mdetail-params">
41637      * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
41638      * or <code>{@link Ext.Component#id id}</code> of the child component </li>
41639      * <li>a <b><code>Number</code></b> : representing the position of the child component
41640      * within the <code>{@link #items}</code> <b>property</b></li>
41641      * </ul></div>
41642      * <p>For additional information see {@link Ext.util.MixedCollection#get}.
41643      * @return Ext.Component The component (if found).
41644      */
41645     getComponent : function(comp) {
41646         if (Ext.isObject(comp)) {
41647             comp = comp.getItemId();
41648         }
41649
41650         return this.items.get(comp);
41651     },
41652
41653     /**
41654      * Retrieves all descendant components which match the passed selector.
41655      * Executes an Ext.ComponentQuery.query using this container as its root.
41656      * @param {String} selector Selector complying to an Ext.ComponentQuery selector
41657      * @return {Array} Ext.Component's which matched the selector
41658      */
41659     query : function(selector) {
41660         return Ext.ComponentQuery.query(selector, this);
41661     },
41662
41663     /**
41664      * Retrieves the first direct child of this container which matches the passed selector.
41665      * The passed in selector must comply with an Ext.ComponentQuery selector.
41666      * @param {String} selector An Ext.ComponentQuery selector
41667      * @return Ext.Component
41668      */
41669     child : function(selector) {
41670         return this.query('> ' + selector)[0] || null;
41671     },
41672
41673     /**
41674      * Retrieves the first descendant of this container which matches the passed selector.
41675      * The passed in selector must comply with an Ext.ComponentQuery selector.
41676      * @param {String} selector An Ext.ComponentQuery selector
41677      * @return Ext.Component
41678      */
41679     down : function(selector) {
41680         return this.query(selector)[0] || null;
41681     },
41682
41683     // inherit docs
41684     show : function() {
41685         this.callParent(arguments);
41686         this.performDeferredLayouts();
41687         return this;
41688     },
41689
41690     // Lay out any descendant containers who queued a layout operation during the time this was hidden
41691     // This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops.
41692     performDeferredLayouts: function() {
41693         var layoutCollection = this.layoutOnShow,
41694             ln = layoutCollection.getCount(),
41695             i = 0,
41696             needsLayout,
41697             item;
41698
41699         for (; i < ln; i++) {
41700             item = layoutCollection.get(i);
41701             needsLayout = item.needsLayout;
41702
41703             if (Ext.isObject(needsLayout)) {
41704                 item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
41705             }
41706         }
41707         layoutCollection.clear();
41708     },    
41709     
41710     //@private
41711     // Enable all immediate children that was previously disabled
41712     onEnable: function() {
41713         Ext.Array.each(this.query('[isFormField]'), function(item) {
41714             if (item.resetDisable) {
41715                 item.enable();
41716                 delete item.resetDisable;             
41717             }
41718         });
41719         this.callParent();
41720     },
41721     
41722     // @private
41723     // Disable all immediate children that was previously disabled
41724     onDisable: function() {
41725         Ext.Array.each(this.query('[isFormField]'), function(item) {
41726             if (item.resetDisable !== false && !item.disabled) {
41727                 item.disable();
41728                 item.resetDisable = true;
41729             }
41730         });
41731         this.callParent();
41732     },
41733
41734     /**
41735      * Occurs before componentLayout is run. Returning false from this method will prevent the containerLayout
41736      * from being executed.
41737      */
41738     beforeLayout: function() {
41739         return true;
41740     },
41741
41742     // @private
41743     beforeDestroy : function() {
41744         var me = this,
41745             items = me.items,
41746             c;
41747
41748         if (items) {
41749             while ((c = items.first())) {
41750                 me.doRemove(c, true);
41751             }
41752         }
41753
41754         Ext.destroy(
41755             me.layout,
41756             me.floatingItems
41757         );
41758         me.callParent();
41759     }
41760 });
41761 /**
41762  * @class Ext.container.Container
41763  * @extends Ext.container.AbstractContainer
41764  * <p>Base class for any {@link Ext.Component} that may contain other Components. Containers handle the
41765  * basic behavior of containing items, namely adding, inserting and removing items.</p>
41766  *
41767  * <p>The most commonly used Container classes are {@link Ext.panel.Panel}, {@link Ext.window.Window} and {@link Ext.tab.Panel}.
41768  * If you do not need the capabilities offered by the aforementioned classes you can create a lightweight
41769  * Container to be encapsulated by an HTML element to your specifications by using the
41770  * <code><b>{@link Ext.Component#autoEl autoEl}</b></code> config option.</p>
41771  *
41772  * {@img Ext.Container/Ext.Container.png Ext.Container component} 
41773  * <p>The code below illustrates how to explicitly create a Container:<pre><code>
41774 // explicitly create a Container
41775 Ext.create('Ext.container.Container', {
41776     layout: {
41777         type: 'hbox'
41778     },
41779     width: 400,
41780     renderTo: Ext.getBody(),
41781     border: 1,
41782     style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
41783     defaults: {
41784         labelWidth: 80,
41785         // implicitly create Container by specifying xtype
41786         xtype: 'datefield',
41787         flex: 1,
41788         style: {
41789             padding: '10px'
41790         }
41791     },
41792     items: [{
41793         xtype: 'datefield',
41794         name: 'startDate',
41795         fieldLabel: 'Start date'
41796     },{
41797         xtype: 'datefield',
41798         name: 'endDate',
41799         fieldLabel: 'End date'
41800     }]
41801 });
41802 </code></pre></p>
41803  *
41804  * <p><u><b>Layout</b></u></p>
41805  * <p>Container classes delegate the rendering of child Components to a layout
41806  * manager class which must be configured into the Container using the
41807  * <code><b>{@link #layout}</b></code> configuration property.</p>
41808  * <p>When either specifying child <code>{@link #items}</code> of a Container,
41809  * or dynamically {@link #add adding} Components to a Container, remember to
41810  * consider how you wish the Container to arrange those child elements, and
41811  * whether those child elements need to be sized using one of Ext's built-in
41812  * <b><code>{@link #layout}</code></b> schemes. By default, Containers use the
41813  * {@link Ext.layout.container.Auto Auto} scheme which only
41814  * renders child components, appending them one after the other inside the
41815  * Container, and <b>does not apply any sizing</b> at all.</p>
41816  * <p>A common mistake is when a developer neglects to specify a
41817  * <b><code>{@link #layout}</code></b> (e.g. widgets like GridPanels or
41818  * TreePanels are added to Containers for which no <code><b>{@link #layout}</b></code>
41819  * has been specified). If a Container is left to use the default
41820  * {Ext.layout.container.Auto Auto} scheme, none of its
41821  * child components will be resized, or changed in any way when the Container
41822  * is resized.</p>
41823  * <p>Certain layout managers allow dynamic addition of child components.
41824  * Those that do include {@link Ext.layout.container.Card},
41825  * {@link Ext.layout.container.Anchor}, {@link Ext.layout.container.VBox}, {@link Ext.layout.container.HBox}, and
41826  * {@link Ext.layout.container.Table}. For example:<pre><code>
41827 //  Create the GridPanel.
41828 var myNewGrid = new Ext.grid.Panel({
41829     store: myStore,
41830     headers: myHeaders,
41831     title: 'Results', // the title becomes the title of the tab
41832 });
41833
41834 myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
41835 myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
41836  * </code></pre></p>
41837  * <p>The example above adds a newly created GridPanel to a TabPanel. Note that
41838  * a TabPanel uses {@link Ext.layout.container.Card} as its layout manager which
41839  * means all its child items are sized to {@link Ext.layout.container.Fit fit}
41840  * exactly into its client area.
41841  * <p><b><u>Overnesting is a common problem</u></b>.
41842  * An example of overnesting occurs when a GridPanel is added to a TabPanel
41843  * by wrapping the GridPanel <i>inside</i> a wrapping Panel (that has no
41844  * <code><b>{@link #layout}</b></code> specified) and then add that wrapping Panel
41845  * to the TabPanel. The point to realize is that a GridPanel <b>is</b> a
41846  * Component which can be added directly to a Container. If the wrapping Panel
41847  * has no <code><b>{@link #layout}</b></code> configuration, then the overnested
41848  * GridPanel will not be sized as expected.<p>
41849  *
41850  * <p><u><b>Adding via remote configuration</b></u></p>
41851  *
41852  * <p>A server side script can be used to add Components which are generated dynamically on the server.
41853  * An example of adding a GridPanel to a TabPanel where the GridPanel is generated by the server
41854  * based on certain parameters:
41855  * </p><pre><code>
41856 // execute an Ajax request to invoke server side script:
41857 Ext.Ajax.request({
41858     url: 'gen-invoice-grid.php',
41859     // send additional parameters to instruct server script
41860     params: {
41861         startDate: Ext.getCmp('start-date').getValue(),
41862         endDate: Ext.getCmp('end-date').getValue()
41863     },
41864     // process the response object to add it to the TabPanel:
41865     success: function(xhr) {
41866         var newComponent = eval(xhr.responseText); // see discussion below
41867         myTabPanel.add(newComponent); // add the component to the TabPanel
41868         myTabPanel.setActiveTab(newComponent);
41869     },
41870     failure: function() {
41871         Ext.Msg.alert("Grid create failed", "Server communication failure");
41872     }
41873 });
41874 </code></pre>
41875  * <p>The server script needs to return a JSON representation of a configuration object, which, when decoded
41876  * will return a config object with an {@link Ext.Component#xtype xtype}. The server might return the following
41877  * JSON:</p><pre><code>
41878 {
41879     "xtype": 'grid',
41880     "title": 'Invoice Report',
41881     "store": {
41882         "model": 'Invoice',
41883         "proxy": {
41884             "type": 'ajax',
41885             "url": 'get-invoice-data.php',
41886             "reader": {
41887                 "type": 'json'
41888                 "record": 'transaction',
41889                 "idProperty": 'id',
41890                 "totalRecords": 'total'
41891             })
41892         },
41893         "autoLoad": {
41894             "params": {
41895                 "startDate": '01/01/2008',
41896                 "endDate": '01/31/2008'
41897             }
41898         }
41899     },
41900     "headers": [
41901         {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
41902         {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
41903         {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
41904         {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
41905     ]
41906 }
41907 </code></pre>
41908  * <p>When the above code fragment is passed through the <code>eval</code> function in the success handler
41909  * of the Ajax request, the result will be a config object which, when added to a Container, will cause instantiation
41910  * of a GridPanel. <b>Be sure that the Container is configured with a layout which sizes and positions the child items to your requirements.</b></p>
41911  * <p>Note: since the code above is <i>generated</i> by a server script, the <code>autoLoad</code> params for
41912  * the Store, the user's preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel
41913  * can all be generated into the code since these are all known on the server.</p>
41914  *
41915  * @xtype container
41916  */
41917 Ext.define('Ext.container.Container', {
41918     extend: 'Ext.container.AbstractContainer',
41919     alias: 'widget.container',
41920     alternateClassName: 'Ext.Container',
41921
41922     /**
41923      * Return the immediate child Component in which the passed element is located.
41924      * @param el The element to test.
41925      * @return {Component} The child item which contains the passed element.
41926      */
41927     getChildByElement: function(el) {
41928         var item,
41929             itemEl,
41930             i = 0,
41931             it = this.items.items,
41932             ln = it.length;
41933
41934         el = Ext.getDom(el);
41935         for (; i < ln; i++) {
41936             item = it[i];
41937             itemEl = item.getEl();
41938             if ((itemEl.dom === el) || itemEl.contains(el)) {
41939                 return item;
41940             }
41941         }
41942         return null;
41943     }
41944 });
41945
41946 /**
41947  * @class Ext.toolbar.Fill
41948  * @extends Ext.Component
41949  *
41950  * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
41951  * the right-justified button container.
41952  *
41953  * {@img Ext.toolbar.Fill/Ext.toolbar.Fill.png Toolbar Fill}
41954  *
41955  * ## Example
41956  *
41957  *     Ext.create('Ext.panel.Panel', {
41958  *          title: 'Toolbar Fill Example',
41959  *          width: 300,
41960  *          height: 200,
41961  *          tbar : [
41962  *              'Item 1',
41963  *              {xtype: 'tbfill'}, // or '->'
41964  *              'Item 2'
41965  *          ],
41966  *          renderTo: Ext.getBody()
41967  *      });
41968  *
41969  * @constructor
41970  * Creates a new Fill
41971  * @xtype tbfill
41972  */
41973 Ext.define('Ext.toolbar.Fill', {
41974     extend: 'Ext.Component',
41975     alias: 'widget.tbfill',
41976     alternateClassName: 'Ext.Toolbar.Fill',
41977     isFill : true,
41978     flex: 1
41979 });
41980 /**
41981  * @class Ext.toolbar.Item
41982  * @extends Ext.Component
41983  * The base class that other non-interacting Toolbar Item classes should extend in order to
41984  * get some basic common toolbar item functionality.
41985  * @constructor
41986  * Creates a new Item
41987  * @param {HTMLElement} el
41988  * @xtype tbitem
41989  */
41990 Ext.define('Ext.toolbar.Item', {
41991     extend: 'Ext.Component',
41992     alias: 'widget.tbitem',
41993     alternateClassName: 'Ext.Toolbar.Item',
41994     enable:Ext.emptyFn,
41995     disable:Ext.emptyFn,
41996     focus:Ext.emptyFn
41997     /**
41998      * @cfg {String} overflowText Text to be used for the menu if the item is overflowed.
41999      */
42000 });
42001 /**
42002  * @class Ext.toolbar.Separator
42003  * @extends Ext.toolbar.Item
42004  * A simple class that adds a vertical separator bar between toolbar items
42005  * (css class:<tt>'x-toolbar-separator'</tt>). 
42006  *
42007  * {@img Ext.toolbar.Separator/Ext.toolbar.Separator.png Toolbar Separator}
42008  *
42009  * ## Example
42010  *
42011  *     Ext.create('Ext.panel.Panel', {
42012  *         title: 'Toolbar Seperator Example',
42013  *         width: 300,
42014  *         height: 200,
42015  *         tbar : [
42016  *             'Item 1',
42017  *             {xtype: 'tbseparator'}, // or '-'
42018  *             'Item 2'
42019  *         ],
42020  *         renderTo: Ext.getBody()
42021  *     }); 
42022  *
42023  * @constructor
42024  * Creates a new Separator
42025  * @xtype tbseparator
42026  */
42027 Ext.define('Ext.toolbar.Separator', {
42028     extend: 'Ext.toolbar.Item',
42029     alias: 'widget.tbseparator',
42030     alternateClassName: 'Ext.Toolbar.Separator',
42031     baseCls: Ext.baseCSSPrefix + 'toolbar-separator',
42032     focusable: false
42033 });
42034 /**
42035  * @class Ext.menu.Manager
42036  * Provides a common registry of all menus on a page.
42037  * @singleton
42038  */
42039 Ext.define('Ext.menu.Manager', {
42040     singleton: true,
42041     requires: [
42042         'Ext.util.MixedCollection',
42043         'Ext.util.KeyMap'
42044     ],
42045     alternateClassName: 'Ext.menu.MenuMgr',
42046
42047     uses: ['Ext.menu.Menu'],
42048
42049     menus: {},
42050     groups: {},
42051     attached: false,
42052     lastShow: new Date(),
42053
42054     init: function() {
42055         var me = this;
42056         
42057         me.active = Ext.create('Ext.util.MixedCollection');
42058         Ext.getDoc().addKeyListener(27, function() {
42059             if (me.active.length > 0) {
42060                 me.hideAll();
42061             }
42062         }, me);
42063     },
42064
42065     /**
42066      * Hides all menus that are currently visible
42067      * @return {Boolean} success True if any active menus were hidden.
42068      */
42069     hideAll: function() {
42070         var active = this.active,
42071             c;
42072         if (active && active.length > 0) {
42073             c = active.clone();
42074             c.each(function(m) {
42075                 m.hide();
42076             });
42077             return true;
42078         }
42079         return false;
42080     },
42081
42082     onHide: function(m) {
42083         var me = this,
42084             active = me.active;
42085         active.remove(m);
42086         if (active.length < 1) {
42087             Ext.getDoc().un('mousedown', me.onMouseDown, me);
42088             me.attached = false;
42089         }
42090     },
42091
42092     onShow: function(m) {
42093         var me = this,
42094             active   = me.active,
42095             last     = active.last(),
42096             attached = me.attached,
42097             menuEl   = m.getEl(),
42098             zIndex;
42099
42100         me.lastShow = new Date();
42101         active.add(m);
42102         if (!attached) {
42103             Ext.getDoc().on('mousedown', me.onMouseDown, me);
42104             me.attached = true;
42105         }
42106         m.toFront();
42107     },
42108
42109     onBeforeHide: function(m) {
42110         if (m.activeChild) {
42111             m.activeChild.hide();
42112         }
42113         if (m.autoHideTimer) {
42114             clearTimeout(m.autoHideTimer);
42115             delete m.autoHideTimer;
42116         }
42117     },
42118
42119     onBeforeShow: function(m) {
42120         var active = this.active,
42121             parentMenu = m.parentMenu;
42122             
42123         active.remove(m);
42124         if (!parentMenu && !m.allowOtherMenus) {
42125             this.hideAll();
42126         }
42127         else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
42128             parentMenu.activeChild.hide();
42129         }
42130     },
42131
42132     // private
42133     onMouseDown: function(e) {
42134         var me = this,
42135             active = me.active,
42136             lastShow = me.lastShow;
42137
42138         if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
42139             me.hideAll();
42140         }
42141     },
42142
42143     // private
42144     register: function(menu) {
42145         var me = this;
42146
42147         if (!me.active) {
42148             me.init();
42149         }
42150
42151         if (menu.floating) {
42152             me.menus[menu.id] = menu;
42153             menu.on({
42154                 beforehide: me.onBeforeHide,
42155                 hide: me.onHide,
42156                 beforeshow: me.onBeforeShow,
42157                 show: me.onShow,
42158                 scope: me
42159             });
42160         }
42161     },
42162
42163     /**
42164      * Returns a {@link Ext.menu.Menu} object
42165      * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
42166      * be used to generate and return a new Menu this.
42167      * @return {Ext.menu.Menu} The specified menu, or null if none are found
42168      */
42169     get: function(menu) {
42170         var menus = this.menus;
42171         
42172         if (typeof menu == 'string') { // menu id
42173             if (!menus) {  // not initialized, no menus to return
42174                 return null;
42175             }
42176             return menus[menu];
42177         } else if (menu.isMenu) {  // menu instance
42178             return menu;
42179         } else if (Ext.isArray(menu)) { // array of menu items
42180             return Ext.create('Ext.menu.Menu', {items:menu});
42181         } else { // otherwise, must be a config
42182             return Ext.ComponentManager.create(menu, 'menu');
42183         }
42184     },
42185
42186     // private
42187     unregister: function(menu) {
42188         var me = this,
42189             menus = me.menus,
42190             active = me.active;
42191
42192         delete menus[menu.id];
42193         active.remove(menu);
42194         menu.un({
42195             beforehide: me.onBeforeHide,
42196             hide: me.onHide,
42197             beforeshow: me.onBeforeShow,
42198             show: me.onShow,
42199             scope: me
42200         });
42201     },
42202
42203     // private
42204     registerCheckable: function(menuItem) {
42205         var groups  = this.groups,
42206             groupId = menuItem.group;
42207
42208         if (groupId) {
42209             if (!groups[groupId]) {
42210                 groups[groupId] = [];
42211             }
42212
42213             groups[groupId].push(menuItem);
42214         }
42215     },
42216
42217     // private
42218     unregisterCheckable: function(menuItem) {
42219         var groups  = this.groups,
42220             groupId = menuItem.group;
42221
42222         if (groupId) {
42223             Ext.Array.remove(groups[groupId], menuItem);
42224         }
42225     },
42226
42227     onCheckChange: function(menuItem, state) {
42228         var groups  = this.groups,
42229             groupId = menuItem.group,
42230             i       = 0,
42231             group, ln, curr;
42232
42233         if (groupId && state) {
42234             group = groups[groupId];
42235             ln = group.length;
42236             for (; i < ln; i++) {
42237                 curr = group[i];
42238                 if (curr != menuItem) {
42239                     curr.setChecked(false);
42240                 }
42241             }
42242         }
42243     }
42244 });
42245 /**
42246  * @class Ext.button.Button
42247  * @extends Ext.Component
42248
42249 Create simple buttons with this component. Customisations include {@link #config-iconAlign aligned}
42250 {@link #config-iconCls icons}, {@link #config-menu dropdown menus}, {@link #config-tooltip tooltips}
42251 and {@link #config-scale sizing options}. Specify a {@link #config-handler handler} to run code when
42252 a user clicks the button, or use {@link #config-listeners listeners} for other events such as
42253 {@link #events-mouseover mouseover}.
42254
42255 {@img Ext.button.Button/Ext.button.Button1.png Ext.button.Button component}
42256 Example usage:
42257
42258     Ext.create('Ext.Button', {
42259         text: 'Click me',
42260         renderTo: Ext.getBody(),        
42261         handler: function() {
42262             alert('You clicked the button!')
42263         }
42264     });
42265
42266 The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler} method.
42267 Example usage:
42268
42269     Ext.create('Ext.Button', {
42270         text    : 'Dyanmic Handler Button',
42271         renderTo: Ext.getBody(),
42272         handler : function() {
42273             //this button will spit out a different number every time you click it.
42274             //so firstly we must check if that number is already set:
42275             if (this.clickCount) {
42276                 //looks like the property is already set, so lets just add 1 to that number and alert the user
42277                 this.clickCount++;
42278                 alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
42279             } else {
42280                 //if the clickCount property is not set, we will set it and alert the user
42281                 this.clickCount = 1;
42282                 alert('You just clicked the button for the first time!\n\nTry pressing it again..');
42283             }
42284         }
42285     });
42286
42287 A button within a container:
42288
42289     Ext.create('Ext.Container', {
42290         renderTo: Ext.getBody(),
42291         items   : [
42292             {
42293                 xtype: 'button',
42294                 text : 'My Button'
42295             }
42296         ]
42297     });
42298
42299 A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
42300 * `'small'`
42301 * `'medium'`
42302 * `'large'`
42303
42304 {@img Ext.button.Button/Ext.button.Button2.png Ext.button.Button component}
42305 Example usage:
42306
42307     Ext.create('Ext.Button', {
42308         renderTo: document.body,
42309         text    : 'Click me',
42310         scale   : 'large'
42311     });
42312
42313 Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
42314 {@img Ext.button.Button/Ext.button.Button3.png Ext.button.Button component}
42315 Example usage:
42316
42317     Ext.create('Ext.Button', {
42318         renderTo: Ext.getBody(),
42319         text: 'Click Me',
42320         enableToggle: true
42321     });
42322
42323 You can assign a menu to a button by using the {@link #menu} configuration. This standard configuration can either be a reference to a {@link Ext.menu.Menu menu}
42324 object, a {@link Ext.menu.Menu menu} id or a {@link Ext.menu.Menu menu} config blob. When assigning a menu to a button, an arrow is automatically added to the button.
42325 You can change the alignment of the arrow using the {@link #arrowAlign} configuration on button.
42326 {@img Ext.button.Button/Ext.button.Button4.png Ext.button.Button component}
42327 Example usage:
42328
42329     Ext.create('Ext.Button', {
42330         text      : 'Menu button',
42331         renderTo  : Ext.getBody(),        
42332         arrowAlign: 'bottom',
42333         menu      : [
42334             {text: 'Item 1'},
42335             {text: 'Item 2'},
42336             {text: 'Item 3'},
42337             {text: 'Item 4'}
42338         ]
42339     });
42340
42341 Using listeners, you can easily listen to events fired by any component, using the {@link #listeners} configuration or using the {@link #addListener} method.
42342 Button has a variety of different listeners:
42343 * `click`
42344 * `toggle`
42345 * `mouseover`
42346 * `mouseout`
42347 * `mouseshow`
42348 * `menuhide`
42349 * `menutriggerover`
42350 * `menutriggerout`
42351
42352 Example usage:
42353
42354     Ext.create('Ext.Button', {
42355         text     : 'Button',
42356         renderTo : Ext.getBody(),
42357         listeners: {
42358             click: function() {
42359                 //this == the button, as we are in the local scope
42360                 this.setText('I was clicked!');
42361             },
42362             mouseover: function() {
42363                 //set a new config which says we moused over, if not already set
42364                 if (!this.mousedOver) {
42365                     this.mousedOver = true;
42366                     alert('You moused over a button!\n\nI wont do this again.');
42367                 }
42368             }
42369         }
42370     });
42371
42372  * @constructor
42373  * Create a new button
42374  * @param {Object} config The config object
42375  * @xtype button
42376  * @markdown
42377  * @docauthor Robert Dougan <rob@sencha.com>
42378  */
42379 Ext.define('Ext.button.Button', {
42380
42381     /* Begin Definitions */
42382     alias: 'widget.button',
42383     extend: 'Ext.Component',
42384
42385     requires: [
42386         'Ext.menu.Manager',
42387         'Ext.util.ClickRepeater',
42388         'Ext.layout.component.Button',
42389         'Ext.util.TextMetrics',
42390         'Ext.util.KeyMap'
42391     ],
42392
42393     alternateClassName: 'Ext.Button',
42394     /* End Definitions */
42395
42396     isButton: true,
42397     componentLayout: 'button',
42398
42399     /**
42400      * Read-only. True if this button is hidden
42401      * @type Boolean
42402      */
42403     hidden: false,
42404
42405     /**
42406      * Read-only. True if this button is disabled
42407      * @type Boolean
42408      */
42409     disabled: false,
42410
42411     /**
42412      * Read-only. True if this button is pressed (only if enableToggle = true)
42413      * @type Boolean
42414      */
42415     pressed: false,
42416
42417     /**
42418      * @cfg {String} text The button text to be used as innerHTML (html tags are accepted)
42419      */
42420
42421     /**
42422      * @cfg {String} icon The path to an image to display in the button (the image will be set as the background-image
42423      * CSS property of the button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
42424      */
42425
42426     /**
42427      * @cfg {Function} handler A function called when the button is clicked (can be used instead of click event).
42428      * The handler is passed the following parameters:<div class="mdetail-params"><ul>
42429      * <li><code>b</code> : Button<div class="sub-desc">This Button.</div></li>
42430      * <li><code>e</code> : EventObject<div class="sub-desc">The click event.</div></li>
42431      * </ul></div>
42432      */
42433
42434     /**
42435      * @cfg {Number} minWidth The minimum width for this button (used to give a set of buttons a common width).
42436      * See also {@link Ext.panel.Panel}.<tt>{@link Ext.panel.Panel#minButtonWidth minButtonWidth}</tt>.
42437      */
42438
42439     /**
42440      * @cfg {String/Object} tooltip The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or QuickTips config object
42441      */
42442
42443     /**
42444      * @cfg {Boolean} hidden True to start hidden (defaults to false)
42445      */
42446
42447     /**
42448      * @cfg {Boolean} disabled True to start disabled (defaults to false)
42449      */
42450
42451     /**
42452      * @cfg {Boolean} pressed True to start pressed (only if enableToggle = true)
42453      */
42454
42455     /**
42456      * @cfg {String} toggleGroup The group this toggle button is a member of (only 1 per group can be pressed)
42457      */
42458
42459     /**
42460      * @cfg {Boolean/Object} repeat True to repeat fire the click event while the mouse is down. This can also be
42461      * a {@link Ext.util.ClickRepeater ClickRepeater} config object (defaults to false).
42462      */
42463
42464     /**
42465      * @cfg {Number} tabIndex Set a DOM tabIndex for this button (defaults to undefined)
42466      */
42467
42468     /**
42469      * @cfg {Boolean} allowDepress
42470      * False to not allow a pressed Button to be depressed (defaults to undefined). Only valid when {@link #enableToggle} is true.
42471      */
42472
42473     /**
42474      * @cfg {Boolean} enableToggle
42475      * True to enable pressed/not pressed toggling (defaults to false)
42476      */
42477     enableToggle: false,
42478
42479     /**
42480      * @cfg {Function} toggleHandler
42481      * Function called when a Button with {@link #enableToggle} set to true is clicked. Two arguments are passed:<ul class="mdetail-params">
42482      * <li><b>button</b> : Ext.button.Button<div class="sub-desc">this Button object</div></li>
42483      * <li><b>state</b> : Boolean<div class="sub-desc">The next state of the Button, true means pressed.</div></li>
42484      * </ul>
42485      */
42486
42487     /**
42488      * @cfg {Mixed} menu
42489      * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob (defaults to undefined).
42490      */
42491
42492     /**
42493      * @cfg {String} menuAlign
42494      * The position to align the menu to (see {@link Ext.core.Element#alignTo} for more details, defaults to 'tl-bl?').
42495      */
42496     menuAlign: 'tl-bl?',
42497
42498     /**
42499      * @cfg {String} overflowText If used in a {@link Ext.toolbar.Toolbar Toolbar}, the
42500      * text to be used if this item is shown in the overflow menu. See also
42501      * {@link Ext.toolbar.Item}.<code>{@link Ext.toolbar.Item#overflowText overflowText}</code>.
42502      */
42503
42504     /**
42505      * @cfg {String} iconCls
42506      * A css class which sets a background image to be used as the icon for this button
42507      */
42508
42509     /**
42510      * @cfg {String} type
42511      * submit, reset or button - defaults to 'button'
42512      */
42513     type: 'button',
42514
42515     /**
42516      * @cfg {String} clickEvent
42517      * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
42518      * Defaults to <tt>'click'</tt>.
42519      */
42520     clickEvent: 'click',
42521     
42522     /**
42523      * @cfg {Boolean} preventDefault
42524      * True to prevent the default action when the {@link #clickEvent} is processed. Defaults to true.
42525      */
42526     preventDefault: true,
42527
42528     /**
42529      * @cfg {Boolean} handleMouseEvents
42530      * False to disable visual cues on mouseover, mouseout and mousedown (defaults to true)
42531      */
42532     handleMouseEvents: true,
42533
42534     /**
42535      * @cfg {String} tooltipType
42536      * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
42537      */
42538     tooltipType: 'qtip',
42539
42540     /**
42541      * @cfg {String} baseCls
42542      * The base CSS class to add to all buttons. (Defaults to 'x-btn')
42543      */
42544     baseCls: Ext.baseCSSPrefix + 'btn',
42545
42546     /**
42547      * @cfg {String} pressedCls
42548      * The CSS class to add to a button when it is in the pressed state. (Defaults to 'x-btn-pressed')
42549      */
42550     pressedCls: 'pressed',
42551     
42552     /**
42553      * @cfg {String} overCls
42554      * The CSS class to add to a button when it is in the over (hovered) state. (Defaults to 'x-btn-over')
42555      */
42556     overCls: 'over',
42557     
42558     /**
42559      * @cfg {String} focusCls
42560      * The CSS class to add to a button when it is in the focussed state. (Defaults to 'x-btn-focus')
42561      */
42562     focusCls: 'focus',
42563     
42564     /**
42565      * @cfg {String} menuActiveCls
42566      * The CSS class to add to a button when it's menu is active. (Defaults to 'x-btn-menu-active')
42567      */
42568     menuActiveCls: 'menu-active',
42569     
42570     /**
42571      * @cfg {Object} baseParams
42572      * An object literal of parameters to pass to the url when the {@link #href} property is specified.
42573      */
42574     
42575     /**
42576      * @cfg {Object} params
42577      * An object literal of parameters to pass to the url when the {@link #href} property is specified.
42578      * Any params override {@link #baseParams}. New params can be set using the {@link #setParams} method.
42579      */
42580
42581     ariaRole: 'button',
42582
42583     // inherited
42584     renderTpl:
42585         '<em class="{splitCls}">' +
42586             '<tpl if="href">' +
42587                 '<a href="{href}" target="{target}"<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="link">' +
42588                     '<span class="{baseCls}-inner">{text}</span>' +
42589                 '</a>' +
42590             '</tpl>' +
42591             '<tpl if="!href">' +
42592                 '<button type="{type}" hidefocus="true"' +
42593                     // the autocomplete="off" is required to prevent Firefox from remembering
42594                     // the button's disabled state between page reloads.
42595                     '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="button" autocomplete="off">' +
42596                     '<span class="{baseCls}-inner" style="{innerSpanStyle}">{text}</span>' +
42597                 '</button>' +
42598             '</tpl>' +
42599         '</em>' ,
42600
42601     /**
42602      * @cfg {String} scale
42603      * <p>(Optional) The size of the Button. Three values are allowed:</p>
42604      * <ul class="mdetail-params">
42605      * <li>'small'<div class="sub-desc">Results in the button element being 16px high.</div></li>
42606      * <li>'medium'<div class="sub-desc">Results in the button element being 24px high.</div></li>
42607      * <li>'large'<div class="sub-desc">Results in the button element being 32px high.</div></li>
42608      * </ul>
42609      * <p>Defaults to <b><tt>'small'</tt></b>.</p>
42610      */
42611     scale: 'small',
42612     
42613     /**
42614      * @private An array of allowed scales.
42615      */
42616     allowedScales: ['small', 'medium', 'large'],
42617     
42618     /**
42619      * @cfg {Object} scope The scope (<tt><b>this</b></tt> reference) in which the
42620      * <code>{@link #handler}</code> and <code>{@link #toggleHandler}</code> is
42621      * executed. Defaults to this Button.
42622      */
42623
42624     /**
42625      * @cfg {String} iconAlign
42626      * <p>(Optional) The side of the Button box to render the icon. Four values are allowed:</p>
42627      * <ul class="mdetail-params">
42628      * <li>'top'<div class="sub-desc"></div></li>
42629      * <li>'right'<div class="sub-desc"></div></li>
42630      * <li>'bottom'<div class="sub-desc"></div></li>
42631      * <li>'left'<div class="sub-desc"></div></li>
42632      * </ul>
42633      * <p>Defaults to <b><tt>'left'</tt></b>.</p>
42634      */
42635     iconAlign: 'left',
42636
42637     /**
42638      * @cfg {String} arrowAlign
42639      * <p>(Optional) The side of the Button box to render the arrow if the button has an associated {@link #menu}.
42640      * Two values are allowed:</p>
42641      * <ul class="mdetail-params">
42642      * <li>'right'<div class="sub-desc"></div></li>
42643      * <li>'bottom'<div class="sub-desc"></div></li>
42644      * </ul>
42645      * <p>Defaults to <b><tt>'right'</tt></b>.</p>
42646      */
42647     arrowAlign: 'right',
42648
42649     /**
42650      * @cfg {String} arrowCls
42651      * <p>(Optional) The className used for the inner arrow element if the button has a menu.</p>
42652      */
42653     arrowCls: 'arrow',
42654
42655     /**
42656      * @cfg {Ext.Template} template (Optional)
42657      * <p>A {@link Ext.Template Template} used to create the Button's DOM structure.</p>
42658      * Instances, or subclasses which need a different DOM structure may provide a different
42659      * template layout in conjunction with an implementation of {@link #getTemplateArgs}.
42660      * @type Ext.Template
42661      * @property template
42662      */
42663
42664     /**
42665      * @cfg {String} cls
42666      * A CSS class string to apply to the button's main element.
42667      */
42668
42669     /**
42670      * @property menu
42671      * @type Menu
42672      * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config option.
42673      */
42674
42675     /**
42676      * @cfg {Boolean} autoWidth
42677      * By default, if a width is not specified the button will attempt to stretch horizontally to fit its content.
42678      * If the button is being managed by a width sizing layout (hbox, fit, anchor), set this to false to prevent
42679      * the button from doing this automatic sizing.
42680      * Defaults to <tt>undefined</tt>.
42681      */
42682      
42683     maskOnDisable: false,
42684
42685     // inherit docs
42686     initComponent: function() {
42687         var me = this;
42688         me.callParent(arguments);
42689
42690         me.addEvents(
42691             /**
42692              * @event click
42693              * Fires when this button is clicked
42694              * @param {Button} this
42695              * @param {EventObject} e The click event
42696              */
42697             'click',
42698
42699             /**
42700              * @event toggle
42701              * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
42702              * @param {Button} this
42703              * @param {Boolean} pressed
42704              */
42705             'toggle',
42706
42707             /**
42708              * @event mouseover
42709              * Fires when the mouse hovers over the button
42710              * @param {Button} this
42711              * @param {Event} e The event object
42712              */
42713             'mouseover',
42714
42715             /**
42716              * @event mouseout
42717              * Fires when the mouse exits the button
42718              * @param {Button} this
42719              * @param {Event} e The event object
42720              */
42721             'mouseout',
42722
42723             /**
42724              * @event menushow
42725              * If this button has a menu, this event fires when it is shown
42726              * @param {Button} this
42727              * @param {Menu} menu
42728              */
42729             'menushow',
42730
42731             /**
42732              * @event menuhide
42733              * If this button has a menu, this event fires when it is hidden
42734              * @param {Button} this
42735              * @param {Menu} menu
42736              */
42737             'menuhide',
42738
42739             /**
42740              * @event menutriggerover
42741              * If this button has a menu, this event fires when the mouse enters the menu triggering element
42742              * @param {Button} this
42743              * @param {Menu} menu
42744              * @param {EventObject} e
42745              */
42746             'menutriggerover',
42747
42748             /**
42749              * @event menutriggerout
42750              * If this button has a menu, this event fires when the mouse leaves the menu triggering element
42751              * @param {Button} this
42752              * @param {Menu} menu
42753              * @param {EventObject} e
42754              */
42755             'menutriggerout'
42756         );
42757
42758         if (me.menu) {
42759             // Flag that we'll have a splitCls
42760             me.split = true;
42761
42762             // retrieve menu by id or instantiate instance if needed
42763             me.menu = Ext.menu.Manager.get(me.menu);
42764             me.menu.ownerCt = me;
42765         }
42766
42767         // Accept url as a synonym for href
42768         if (me.url) {
42769             me.href = me.url;
42770         }
42771
42772         // preventDefault defaults to false for links
42773         if (me.href && !me.hasOwnProperty('preventDefault')) {
42774             me.preventDefault = false;
42775         }
42776
42777         if (Ext.isString(me.toggleGroup)) {
42778             me.enableToggle = true;
42779         }
42780
42781     },
42782
42783     // private
42784     initAria: function() {
42785         this.callParent();
42786         var actionEl = this.getActionEl();
42787         if (this.menu) {
42788             actionEl.dom.setAttribute('aria-haspopup', true);
42789         }
42790     },
42791
42792     // inherit docs
42793     getActionEl: function() {
42794         return this.btnEl;
42795     },
42796
42797     // inherit docs
42798     getFocusEl: function() {
42799         return this.btnEl;
42800     },
42801
42802     // private
42803     setButtonCls: function() {
42804         var me = this,
42805             el = me.el,
42806             cls = [];
42807
42808         if (me.useSetClass) {
42809             if (!Ext.isEmpty(me.oldCls)) {
42810                 me.removeClsWithUI(me.oldCls);
42811                 me.removeClsWithUI(me.pressedCls);
42812             }
42813             
42814             // Check whether the button has an icon or not, and if it has an icon, what is th alignment
42815             if (me.iconCls || me.icon) {
42816                 if (me.text) {
42817                     cls.push('icon-text-' + me.iconAlign);
42818                 } else {
42819                     cls.push('icon');
42820                 }
42821             } else if (me.text) {
42822                 cls.push('noicon');
42823             }
42824             
42825             me.oldCls = cls;
42826             me.addClsWithUI(cls);
42827             me.addClsWithUI(me.pressed ? me.pressedCls : null);
42828         }
42829     },
42830     
42831     // private
42832     onRender: function(ct, position) {
42833         // classNames for the button
42834         var me = this,
42835             repeater, btn;
42836             
42837         // Apply the renderData to the template args
42838         Ext.applyIf(me.renderData, me.getTemplateArgs());
42839
42840         // Extract the button and the button wrapping element
42841         Ext.applyIf(me.renderSelectors, {
42842             btnEl  : me.href ? 'a' : 'button',
42843             btnWrap: 'em',
42844             btnInnerEl: '.' + me.baseCls + '-inner'
42845         });
42846         
42847         if (me.scale) {
42848             me.ui = me.ui + '-' + me.scale;
42849         }
42850
42851         // Render internal structure
42852         me.callParent(arguments);
42853
42854         // If it is a split button + has a toolip for the arrow
42855         if (me.split && me.arrowTooltip) {
42856             me.arrowEl.dom[me.tooltipType] = me.arrowTooltip;
42857         }
42858
42859         // Add listeners to the focus and blur events on the element
42860         me.mon(me.btnEl, {
42861             scope: me,
42862             focus: me.onFocus,
42863             blur : me.onBlur
42864         });
42865
42866         // Set btn as a local variable for easy access
42867         btn = me.el;
42868
42869         if (me.icon) {
42870             me.setIcon(me.icon);
42871         }
42872
42873         if (me.iconCls) {
42874             me.setIconCls(me.iconCls);
42875         }
42876
42877         if (me.tooltip) {
42878             me.setTooltip(me.tooltip, true);
42879         }
42880
42881         // Add the mouse events to the button
42882         if (me.handleMouseEvents) {
42883             me.mon(btn, {
42884                 scope: me,
42885                 mouseover: me.onMouseOver,
42886                 mouseout: me.onMouseOut,
42887                 mousedown: me.onMouseDown
42888             });
42889
42890             if (me.split) {
42891                 me.mon(btn, {
42892                     mousemove: me.onMouseMove,
42893                     scope: me
42894                 });
42895             }
42896         }
42897
42898         // Check if the button has a menu
42899         if (me.menu) {
42900             me.mon(me.menu, {
42901                 scope: me,
42902                 show: me.onMenuShow,
42903                 hide: me.onMenuHide
42904             });
42905
42906             me.keyMap = Ext.create('Ext.util.KeyMap', me.el, {
42907                 key: Ext.EventObject.DOWN,
42908                 handler: me.onDownKey,
42909                 scope: me
42910             });
42911         }
42912
42913         // Check if it is a repeat button
42914         if (me.repeat) {
42915             repeater = Ext.create('Ext.util.ClickRepeater', btn, Ext.isObject(me.repeat) ? me.repeat: {});
42916             me.mon(repeater, 'click', me.onRepeatClick, me);
42917         } else {
42918             me.mon(btn, me.clickEvent, me.onClick, me);
42919         }
42920
42921         // Register the button in the toggle manager
42922         Ext.ButtonToggleManager.register(me);
42923     },
42924
42925     /**
42926      * <p>This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used
42927      * to create this Button's DOM structure.</p>
42928      * <p>Instances or subclasses which use a different Template to create a different DOM structure may need to provide their
42929      * own implementation of this method.</p>
42930      * <p>The default implementation which provides data for the default {@link #template} returns an Object containing the
42931      * following properties:</p><div class="mdetail-params"><ul>
42932      * <li><code>type</code> : The &lt;button&gt;'s {@link #type}</li>
42933      * <li><code>splitCls</code> : A CSS class to determine the presence and position of an arrow icon. (<code>'x-btn-arrow'</code> or <code>'x-btn-arrow-bottom'</code> or <code>''</code>)</li>
42934      * <li><code>cls</code> : A CSS class name applied to the Button's main &lt;tbody&gt; element which determines the button's scale and icon alignment.</li>
42935      * <li><code>text</code> : The {@link #text} to display ion the Button.</li>
42936      * <li><code>tabIndex</code> : The tab index within the input flow.</li>
42937      * </ul></div>
42938      * @return {Array} Substitution data for a Template.
42939     */
42940     getTemplateArgs: function() {
42941         var me = this,
42942             persistentPadding = me.getPersistentBtnPadding(),
42943             innerSpanStyle = '';
42944
42945         // Create negative margin offsets to counteract persistent button padding if needed
42946         if (Math.max.apply(Math, persistentPadding) > 0) {
42947             innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
42948                 return -pad + 'px';
42949             }).join(' ');
42950         }
42951
42952         return {
42953             href     : me.getHref(),
42954             target   : me.target || '_blank',
42955             type     : me.type,
42956             splitCls : me.getSplitCls(),
42957             cls      : me.cls,
42958             text     : me.text || '&#160;',
42959             tabIndex : me.tabIndex,
42960             innerSpanStyle: innerSpanStyle
42961         };
42962     },
42963
42964     /**
42965      * @private
42966      * If there is a configured href for this Button, returns the href with parameters appended.
42967      * @returns The href string with parameters appended.
42968      */
42969     getHref: function() {
42970         var me = this,
42971             params = Ext.apply({}, me.baseParams);
42972             
42973         // write baseParams first, then write any params
42974         params = Ext.apply(params, me.params);
42975         return me.href ? Ext.urlAppend(me.href, Ext.Object.toQueryString(params)) : false;
42976     },
42977
42978     /**
42979      * <p><b>Only valid if the Button was originally configured with a {@link #url}</b></p>
42980      * <p>Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.</p>
42981      * @param {Object} params Parameters to use in the href URL.
42982      */
42983     setParams: function(params) {
42984         this.params = params;
42985         this.btnEl.dom.href = this.getHref();
42986     },
42987
42988     getSplitCls: function() {
42989         var me = this;
42990         return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
42991     },
42992
42993     // private
42994     afterRender: function() {
42995         var me = this;
42996         me.useSetClass = true;
42997         me.setButtonCls();
42998         me.doc = Ext.getDoc();
42999         this.callParent(arguments);
43000     },
43001
43002     /**
43003      * Sets the CSS class that provides a background image to use as the button's icon.  This method also changes
43004      * the value of the {@link #iconCls} config internally.
43005      * @param {String} cls The CSS class providing the icon image
43006      * @return {Ext.button.Button} this
43007      */
43008     setIconCls: function(cls) {
43009         var me = this,
43010             btnInnerEl = me.btnInnerEl;
43011         if (btnInnerEl) {
43012             // Remove the previous iconCls from the button
43013             btnInnerEl.removeCls(me.iconCls);
43014             btnInnerEl.addCls(cls || '');
43015             me.setButtonCls();
43016         }
43017         me.iconCls = cls;
43018         return me;
43019     },
43020
43021     /**
43022      * Sets the tooltip for this Button.
43023      * @param {String/Object} tooltip. This may be:<div class="mdesc-details"><ul>
43024      * <li><b>String</b> : A string to be used as innerHTML (html tags are accepted) to show in a tooltip</li>
43025      * <li><b>Object</b> : A configuration object for {@link Ext.tip.QuickTipManager#register}.</li>
43026      * </ul></div>
43027      * @return {Ext.button.Button} this
43028      */
43029     setTooltip: function(tooltip, initial) {
43030         var me = this;
43031
43032         if (me.rendered) {
43033             if (!initial) {
43034                 me.clearTip();
43035             }
43036             if (Ext.isObject(tooltip)) {
43037                 Ext.tip.QuickTipManager.register(Ext.apply({
43038                     target: me.btnEl.id
43039                 },
43040                 tooltip));
43041                 me.tooltip = tooltip;
43042             } else {
43043                 me.btnEl.dom.setAttribute('data-' + this.tooltipType, tooltip);
43044             }
43045         } else {
43046             me.tooltip = tooltip;
43047         }
43048         return me;
43049     },
43050
43051     // private
43052     getRefItems: function(deep){
43053         var menu = this.menu,
43054             items;
43055
43056         if (menu) {
43057             items = menu.getRefItems(deep);
43058             items.unshift(menu);
43059         }
43060         return items || [];
43061     },
43062
43063     // private
43064     clearTip: function() {
43065         if (Ext.isObject(this.tooltip)) {
43066             Ext.tip.QuickTipManager.unregister(this.btnEl);
43067         }
43068     },
43069
43070     // private
43071     beforeDestroy: function() {
43072         var me = this;
43073         if (me.rendered) {
43074             me.clearTip();
43075         }
43076         if (me.menu && me.destroyMenu !== false) {
43077             Ext.destroy(me.btnEl, me.btnInnerEl, me.menu);
43078         }
43079         Ext.destroy(me.repeater);
43080     },
43081
43082     // private
43083     onDestroy: function() {
43084         var me = this;
43085         if (me.rendered) {
43086             me.doc.un('mouseover', me.monitorMouseOver, me);
43087             me.doc.un('mouseup', me.onMouseUp, me);
43088             delete me.doc;
43089             delete me.btnEl;
43090             delete me.btnInnerEl;
43091             Ext.ButtonToggleManager.unregister(me);
43092             
43093             Ext.destroy(me.keyMap);
43094             delete me.keyMap;
43095         }
43096         me.callParent();
43097     },
43098
43099     /**
43100      * Assigns this Button's click handler
43101      * @param {Function} handler The function to call when the button is clicked
43102      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function is executed.
43103      * Defaults to this Button.
43104      * @return {Ext.button.Button} this
43105      */
43106     setHandler: function(handler, scope) {
43107         this.handler = handler;
43108         this.scope = scope;
43109         return this;
43110     },
43111
43112     /**
43113      * Sets this Button's text
43114      * @param {String} text The button text
43115      * @return {Ext.button.Button} this
43116      */
43117     setText: function(text) {
43118         var me = this;
43119         me.text = text;
43120         if (me.el) {
43121             me.btnInnerEl.update(text || '&#160;');
43122             me.setButtonCls();
43123         }
43124         me.doComponentLayout();
43125         return me;
43126     },
43127
43128     /**
43129      * Sets the background image (inline style) of the button.  This method also changes
43130      * the value of the {@link #icon} config internally.
43131      * @param {String} icon The path to an image to display in the button
43132      * @return {Ext.button.Button} this
43133      */
43134     setIcon: function(icon) {
43135         var me = this,
43136             btnInnerEl = me.btnInnerEl;
43137         me.icon = icon;
43138         if (btnInnerEl) {
43139             btnInnerEl.setStyle('background-image', icon ? 'url(' + icon + ')': '');
43140             me.setButtonCls();
43141         }
43142         return me;
43143     },
43144
43145     /**
43146      * Gets the text for this Button
43147      * @return {String} The button text
43148      */
43149     getText: function() {
43150         return this.text;
43151     },
43152
43153     /**
43154      * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
43155      * @param {Boolean} state (optional) Force a particular state
43156      * @param {Boolean} supressEvent (optional) True to stop events being fired when calling this method.
43157      * @return {Ext.button.Button} this
43158      */
43159     toggle: function(state, suppressEvent) {
43160         var me = this;
43161         state = state === undefined ? !me.pressed: !!state;
43162         if (state !== me.pressed) {
43163             if (me.rendered) {
43164                 me[state ? 'addClsWithUI': 'removeClsWithUI'](me.pressedCls);
43165             }
43166             me.btnEl.dom.setAttribute('aria-pressed', state);
43167             me.pressed = state;
43168             if (!suppressEvent) {
43169                 me.fireEvent('toggle', me, state);
43170                 Ext.callback(me.toggleHandler, me.scope || me, [me, state]);
43171             }
43172         }
43173         return me;
43174     },
43175
43176     /**
43177      * Show this button's menu (if it has one)
43178      */
43179     showMenu: function() {
43180         var me = this;
43181         if (me.rendered && me.menu) {
43182             if (me.tooltip) {
43183                 Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.btnEl);
43184             }
43185             if (me.menu.isVisible()) {
43186                 me.menu.hide();
43187             }
43188
43189             me.menu.showBy(me.el, me.menuAlign);
43190         }
43191         return me;
43192     },
43193
43194     /**
43195      * Hide this button's menu (if it has one)
43196      */
43197     hideMenu: function() {
43198         if (this.hasVisibleMenu()) {
43199             this.menu.hide();
43200         }
43201         return this;
43202     },
43203
43204     /**
43205      * Returns true if the button has a menu and it is visible
43206      * @return {Boolean}
43207      */
43208     hasVisibleMenu: function() {
43209         var menu = this.menu;
43210         return menu && menu.rendered && menu.isVisible();
43211     },
43212
43213     // private
43214     onRepeatClick: function(repeat, e) {
43215         this.onClick(e);
43216     },
43217
43218     // private
43219     onClick: function(e) {
43220         var me = this;
43221         if (me.preventDefault || (me.disabled && me.getHref()) && e) {
43222             e.preventDefault();
43223         }
43224         if (e.button !== 0) {
43225             return;
43226         }
43227         if (!me.disabled) {
43228             if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) {
43229                 me.toggle();
43230             }
43231             if (me.menu && !me.hasVisibleMenu() && !me.ignoreNextClick) {
43232                 me.showMenu();
43233             }
43234             me.fireEvent('click', me, e);
43235             if (me.handler) {
43236                 me.handler.call(me.scope || me, me, e);
43237             }
43238             me.onBlur();
43239         }
43240     },
43241
43242     /**
43243      * @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element.
43244      * The targets are interrogated to see what is being entered from where.
43245      * @param e
43246      */
43247     onMouseOver: function(e) {
43248         var me = this;
43249         if (!me.disabled && !e.within(me.el, true, true)) {
43250             me.onMouseEnter(e);
43251         }
43252     },
43253
43254     /**
43255      * @private mouseout handler called when a mouseout event occurs anywhere within the encapsulating element -
43256      * or the mouse leaves the encapsulating element.
43257      * The targets are interrogated to see what is being exited to where.
43258      * @param e
43259      */
43260     onMouseOut: function(e) {
43261         var me = this;
43262         if (!e.within(me.el, true, true)) {
43263             if (me.overMenuTrigger) {
43264                 me.onMenuTriggerOut(e);
43265             }
43266             me.onMouseLeave(e);
43267         }
43268     },
43269
43270     /**
43271      * @private mousemove handler called when the mouse moves anywhere within the encapsulating element.
43272      * The position is checked to determine if the mouse is entering or leaving the trigger area. Using
43273      * mousemove to check this is more resource intensive than we'd like, but it is necessary because
43274      * the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out
43275      * events when needed. In the future we should consider making the trigger a separate element that
43276      * is absolutely positioned and sized over the trigger area.
43277      */
43278     onMouseMove: function(e) {
43279         var me = this,
43280             el = me.el,
43281             over = me.overMenuTrigger,
43282             overlap, btnSize;
43283
43284         if (me.split) {
43285             if (me.arrowAlign === 'right') {
43286                 overlap = e.getX() - el.getX();
43287                 btnSize = el.getWidth();
43288             } else {
43289                 overlap = e.getY() - el.getY();
43290                 btnSize = el.getHeight();
43291             }
43292
43293             if (overlap > (btnSize - me.getTriggerSize())) {
43294                 if (!over) {
43295                     me.onMenuTriggerOver(e);
43296                 }
43297             } else {
43298                 if (over) {
43299                     me.onMenuTriggerOut(e);
43300                 }
43301             }
43302         }
43303     },
43304
43305     /**
43306      * @private Measures the size of the trigger area for menu and split buttons. Will be a width for
43307      * a right-aligned trigger and a height for a bottom-aligned trigger. Cached after first measurement.
43308      */
43309     getTriggerSize: function() {
43310         var me = this,
43311             size = me.triggerSize,
43312             side, sideFirstLetter, undef;
43313             
43314         if (size === undef) {
43315             side = me.arrowAlign;
43316             sideFirstLetter = side.charAt(0);
43317             size = me.triggerSize = me.el.getFrameWidth(sideFirstLetter) + me.btnWrap.getFrameWidth(sideFirstLetter) + (me.frameSize && me.frameSize[side] || 0);
43318         }
43319         return size;
43320     },
43321
43322     /**
43323      * @private virtual mouseenter handler called when it is detected that the mouseout event
43324      * signified the mouse entering the encapsulating element.
43325      * @param e
43326      */
43327     onMouseEnter: function(e) {
43328         var me = this;
43329         me.addClsWithUI(me.overCls);
43330         me.fireEvent('mouseover', me, e);
43331     },
43332
43333     /**
43334      * @private virtual mouseleave handler called when it is detected that the mouseover event
43335      * signified the mouse entering the encapsulating element.
43336      * @param e
43337      */
43338     onMouseLeave: function(e) {
43339         var me = this;
43340         me.removeClsWithUI(me.overCls);
43341         me.fireEvent('mouseout', me, e);
43342     },
43343
43344     /**
43345      * @private virtual mouseenter handler called when it is detected that the mouseover event
43346      * signified the mouse entering the arrow area of the button - the <em>.
43347      * @param e
43348      */
43349     onMenuTriggerOver: function(e) {
43350         var me = this;
43351         me.overMenuTrigger = true;
43352         me.fireEvent('menutriggerover', me, me.menu, e);
43353     },
43354
43355     /**
43356      * @private virtual mouseleave handler called when it is detected that the mouseout event
43357      * signified the mouse leaving the arrow area of the button - the <em>.
43358      * @param e
43359      */
43360     onMenuTriggerOut: function(e) {
43361         var me = this;
43362         delete me.overMenuTrigger;
43363         me.fireEvent('menutriggerout', me, me.menu, e);
43364     },
43365     
43366     // inherit docs
43367     enable : function(silent) {
43368         var me = this;
43369
43370         me.callParent(arguments);
43371         
43372         me.removeClsWithUI('disabled');
43373
43374         return me;
43375     },
43376
43377     // inherit docs
43378     disable : function(silent) {
43379         var me = this;
43380         
43381         me.callParent(arguments);
43382         
43383         me.addClsWithUI('disabled');
43384
43385         return me;
43386     },
43387     
43388     /**
43389      * Method to change the scale of the button. See {@link #scale} for allowed configurations.
43390      * @param {String} scale The scale to change to.
43391      */
43392     setScale: function(scale) {
43393         var me = this,
43394             ui = me.ui.replace('-' + me.scale, '');
43395         
43396         //check if it is an allowed scale
43397         if (!Ext.Array.contains(me.allowedScales, scale)) {
43398             throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
43399         }
43400         
43401         me.scale = scale;
43402         me.setUI(ui);
43403     },
43404     
43405     // inherit docs
43406     setUI: function(ui) {
43407         var me = this;
43408         
43409         //we need to append the scale to the UI, if not already done
43410         if (me.scale && !ui.match(me.scale)) {
43411             ui = ui + '-' + me.scale;
43412         }
43413         
43414         me.callParent([ui]);
43415         
43416         // Set all the state classNames, as they need to include the UI
43417         // me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
43418     },
43419     
43420     // private
43421     onFocus: function(e) {
43422         var me = this;
43423         if (!me.disabled) {
43424             me.addClsWithUI(me.focusCls);
43425         }
43426     },
43427
43428     // private
43429     onBlur: function(e) {
43430         var me = this;
43431         me.removeClsWithUI(me.focusCls);
43432     },
43433
43434     // private
43435     onMouseDown: function(e) {
43436         var me = this;
43437         if (!me.disabled && e.button === 0) {
43438             me.addClsWithUI(me.pressedCls);
43439             me.doc.on('mouseup', me.onMouseUp, me);
43440         }
43441     },
43442     // private
43443     onMouseUp: function(e) {
43444         var me = this;
43445         if (e.button === 0) {
43446             if (!me.pressed) {
43447                 me.removeClsWithUI(me.pressedCls);
43448             }
43449             me.doc.un('mouseup', me.onMouseUp, me);
43450         }
43451     },
43452     // private
43453     onMenuShow: function(e) {
43454         var me = this;
43455         me.ignoreNextClick = 0;
43456         me.addClsWithUI(me.menuActiveCls);
43457         me.fireEvent('menushow', me, me.menu);
43458     },
43459
43460     // private
43461     onMenuHide: function(e) {
43462         var me = this;
43463         me.removeClsWithUI(me.menuActiveCls);
43464         me.ignoreNextClick = Ext.defer(me.restoreClick, 250, me);
43465         me.fireEvent('menuhide', me, me.menu);
43466     },
43467
43468     // private
43469     restoreClick: function() {
43470         this.ignoreNextClick = 0;
43471     },
43472
43473     // private
43474     onDownKey: function() {
43475         var me = this;
43476
43477         if (!me.disabled) {
43478             if (me.menu) {
43479                 me.showMenu();
43480             }
43481         }
43482     },
43483
43484     /**
43485      * @private Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
43486      * element that cannot be removed. This method returns the size of that padding with a one-time detection.
43487      * @return Array [top, right, bottom, left]
43488      */
43489     getPersistentBtnPadding: function() {
43490         var cls = Ext.button.Button,
43491             padding = cls.persistentPadding,
43492             btn, leftTop, btnEl, btnInnerEl;
43493
43494         if (!padding) {
43495             padding = cls.persistentPadding = [0, 0, 0, 0]; //set early to prevent recursion
43496
43497             if (!Ext.isIE) { //short-circuit IE as it sometimes gives false positive for padding
43498                 // Create auto-size button offscreen and measure its insides
43499                 btn = Ext.create('Ext.button.Button', {
43500                     renderTo: Ext.getBody(),
43501                     text: 'test',
43502                     style: 'position:absolute;top:-999px;'
43503                 });
43504                 btnEl = btn.btnEl;
43505                 btnInnerEl = btn.btnInnerEl;
43506                 btnEl.setSize(null, null); //clear any hard dimensions on the button el to see what it does naturally
43507
43508                 leftTop = btnInnerEl.getOffsetsTo(btnEl);
43509                 padding[0] = leftTop[1];
43510                 padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
43511                 padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
43512                 padding[3] = leftTop[0];
43513
43514                 btn.destroy();
43515             }
43516         }
43517
43518         return padding;
43519     }
43520
43521 }, function() {
43522     var groups = {},
43523         g, i, l;
43524
43525     function toggleGroup(btn, state) {
43526         if (state) {
43527             g = groups[btn.toggleGroup];
43528             for (i = 0, l = g.length; i < l; i++) {
43529                 if (g[i] !== btn) {
43530                     g[i].toggle(false);
43531                 }
43532             }
43533         }
43534     }
43535     // Private utility class used by Button
43536     Ext.ButtonToggleManager = {
43537         register: function(btn) {
43538             if (!btn.toggleGroup) {
43539                 return;
43540             }
43541             var group = groups[btn.toggleGroup];
43542             if (!group) {
43543                 group = groups[btn.toggleGroup] = [];
43544             }
43545             group.push(btn);
43546             btn.on('toggle', toggleGroup);
43547         },
43548
43549         unregister: function(btn) {
43550             if (!btn.toggleGroup) {
43551                 return;
43552             }
43553             var group = groups[btn.toggleGroup];
43554             if (group) {
43555                 Ext.Array.remove(group, btn);
43556                 btn.un('toggle', toggleGroup);
43557             }
43558         },
43559
43560         /**
43561         * Gets the pressed button in the passed group or null
43562         * @param {String} group
43563         * @return Button
43564         */
43565         getPressed: function(group) {
43566             var g = groups[group],
43567                 i = 0,
43568                 len;
43569             if (g) {
43570                 for (len = g.length; i < len; i++) {
43571                     if (g[i].pressed === true) {
43572                         return g[i];
43573                     }
43574                 }
43575             }
43576             return null;
43577         }
43578     };
43579 });
43580
43581 /**
43582  * @class Ext.layout.container.boxOverflow.Menu
43583  * @extends Ext.layout.container.boxOverflow.None
43584  * @private
43585  */
43586 Ext.define('Ext.layout.container.boxOverflow.Menu', {
43587
43588     /* Begin Definitions */
43589
43590     extend: 'Ext.layout.container.boxOverflow.None',
43591     requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],
43592     alternateClassName: 'Ext.layout.boxOverflow.Menu',
43593     
43594     /* End Definitions */
43595
43596     /**
43597      * @cfg {String} afterCtCls
43598      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
43599      * which must always be present at the rightmost edge of the Container
43600      */
43601
43602     /**
43603      * @property noItemsMenuText
43604      * @type String
43605      * HTML fragment to render into the toolbar overflow menu if there are no items to display
43606      */
43607     noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items">(None)</div>',
43608
43609     constructor: function(layout) {
43610         var me = this;
43611
43612         me.callParent(arguments);
43613
43614         // Before layout, we need to re-show all items which we may have hidden due to a previous overflow.
43615         layout.beforeLayout = Ext.Function.createInterceptor(layout.beforeLayout, this.clearOverflow, this);
43616
43617         me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-menu-' + layout.parallelAfter;
43618         /**
43619          * @property menuItems
43620          * @type Array
43621          * Array of all items that are currently hidden and should go into the dropdown menu
43622          */
43623         me.menuItems = [];
43624     },
43625     
43626     onRemove: function(comp){
43627         Ext.Array.remove(this.menuItems, comp);
43628     },
43629
43630     handleOverflow: function(calculations, targetSize) {
43631         var me = this,
43632             layout = me.layout,
43633             methodName = 'get' + layout.parallelPrefixCap,
43634             newSize = {},
43635             posArgs = [null, null];
43636
43637         me.callParent(arguments);
43638         this.createMenu(calculations, targetSize);
43639         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
43640         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - me.afterCt[methodName]();
43641
43642         // Center the menuTrigger button.
43643         // TODO: Should we emulate align: 'middle' like this, or should we 'stretchmax' the menuTrigger?
43644         posArgs[layout.perpendicularSizeIndex] = (calculations.meta.maxSize - me.menuTrigger['get' + layout.perpendicularPrefixCap]()) / 2;
43645         me.menuTrigger.setPosition.apply(me.menuTrigger, posArgs);
43646
43647         return { targetSize: newSize };
43648     },
43649
43650     /**
43651      * @private
43652      * Called by the layout, when it determines that there is no overflow.
43653      * Also called as an interceptor to the layout's onLayout method to reshow
43654      * previously hidden overflowing items.
43655      */
43656     clearOverflow: function(calculations, targetSize) {
43657         var me = this,
43658             newWidth = targetSize ? targetSize.width + (me.afterCt ? me.afterCt.getWidth() : 0) : 0,
43659             items = me.menuItems,
43660             i = 0,
43661             length = items.length,
43662             item;
43663
43664         me.hideTrigger();
43665         for (; i < length; i++) {
43666             items[i].show();
43667         }
43668         items.length = 0;
43669
43670         return targetSize ? {
43671             targetSize: {
43672                 height: targetSize.height,
43673                 width : newWidth
43674             }
43675         } : null;
43676     },
43677
43678     /**
43679      * @private
43680      */
43681     showTrigger: function() {
43682         this.menuTrigger.show();
43683     },
43684
43685     /**
43686      * @private
43687      */
43688     hideTrigger: function() {
43689         if (this.menuTrigger !== undefined) {
43690             this.menuTrigger.hide();
43691         }
43692     },
43693
43694     /**
43695      * @private
43696      * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
43697      */
43698     beforeMenuShow: function(menu) {
43699         var me = this,
43700             items = me.menuItems,
43701             i = 0,
43702             len   = items.length,
43703             item,
43704             prev;
43705
43706         var needsSep = function(group, prev){
43707             return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
43708         };
43709
43710         me.clearMenu();
43711         menu.removeAll();
43712
43713         for (; i < len; i++) {
43714             item = items[i];
43715
43716             // Do not show a separator as a first item
43717             if (!i && (item instanceof Ext.toolbar.Separator)) {
43718                 continue;
43719             }
43720             if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
43721                 menu.add('-');
43722             }
43723
43724             me.addComponentToMenu(menu, item);
43725             prev = item;
43726         }
43727
43728         // put something so the menu isn't empty if no compatible items found
43729         if (menu.items.length < 1) {
43730             menu.add(me.noItemsMenuText);
43731         }
43732     },
43733     
43734     /**
43735      * @private
43736      * Returns a menu config for a given component. This config is used to create a menu item
43737      * to be added to the expander menu
43738      * @param {Ext.Component} component The component to create the config for
43739      * @param {Boolean} hideOnClick Passed through to the menu item
43740      */
43741     createMenuConfig : function(component, hideOnClick) {
43742         var config = Ext.apply({}, component.initialConfig),
43743             group  = component.toggleGroup;
43744
43745         Ext.copyTo(config, component, [
43746             'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
43747         ]);
43748
43749         Ext.apply(config, {
43750             text       : component.overflowText || component.text,
43751             hideOnClick: hideOnClick,
43752             destroyMenu: false
43753         });
43754
43755         if (group || component.enableToggle) {
43756             Ext.apply(config, {
43757                 group  : group,
43758                 checked: component.pressed,
43759                 listeners: {
43760                     checkchange: function(item, checked){
43761                         component.toggle(checked);
43762                     }
43763                 }
43764             });
43765         }
43766
43767         delete config.ownerCt;
43768         delete config.xtype;
43769         delete config.id;
43770         return config;
43771     },
43772
43773     /**
43774      * @private
43775      * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
43776      * @param {Ext.menu.Menu} menu The menu to add to
43777      * @param {Ext.Component} component The component to add
43778      */
43779     addComponentToMenu : function(menu, component) {
43780         var me = this;
43781         if (component instanceof Ext.toolbar.Separator) {
43782             menu.add('-');
43783         } else if (component.isComponent) {
43784             if (component.isXType('splitbutton')) {
43785                 menu.add(me.createMenuConfig(component, true));
43786
43787             } else if (component.isXType('button')) {
43788                 menu.add(me.createMenuConfig(component, !component.menu));
43789
43790             } else if (component.isXType('buttongroup')) {
43791                 component.items.each(function(item){
43792                      me.addComponentToMenu(menu, item);
43793                 });
43794             } else {
43795                 menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));
43796             }
43797         }
43798     },
43799
43800     /**
43801      * @private
43802      * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
43803      * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
43804      */
43805     clearMenu : function() {
43806         var menu = this.moreMenu;
43807         if (menu && menu.items) {
43808             menu.items.each(function(item) {
43809                 if (item.menu) {
43810                     delete item.menu;
43811                 }
43812             });
43813         }
43814     },
43815
43816     /**
43817      * @private
43818      * Creates the overflow trigger and menu used when enableOverflow is set to true and the items
43819      * in the layout are too wide to fit in the space available
43820      */
43821     createMenu: function(calculations, targetSize) {
43822         var me = this,
43823             layout = me.layout,
43824             startProp = layout.parallelBefore,
43825             sizeProp = layout.parallelPrefix,
43826             available = targetSize[sizeProp],
43827             boxes = calculations.boxes,
43828             i = 0,
43829             len = boxes.length,
43830             box;
43831
43832         if (!me.menuTrigger) {
43833             me.createInnerElements();
43834
43835             /**
43836              * @private
43837              * @property menu
43838              * @type Ext.menu.Menu
43839              * The expand menu - holds items for every item that cannot be shown
43840              * because the container is currently not large enough.
43841              */
43842             me.menu = Ext.create('Ext.menu.Menu', {
43843                 hideMode: 'offsets',
43844                 listeners: {
43845                     scope: me,
43846                     beforeshow: me.beforeMenuShow
43847                 }
43848             });
43849
43850             /**
43851              * @private
43852              * @property menuTrigger
43853              * @type Ext.button.Button
43854              * The expand button which triggers the overflow menu to be shown
43855              */
43856             me.menuTrigger = Ext.create('Ext.button.Button', {
43857                 ownerCt : me.layout.owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
43858                 iconCls : Ext.baseCSSPrefix + layout.owner.getXType() + '-more-icon',
43859                 ui      : layout.owner instanceof Ext.toolbar.Toolbar ? 'default-toolbar' : 'default',
43860                 menu    : me.menu,
43861                 getSplitCls: function() { return '';},
43862                 renderTo: me.afterCt
43863             });
43864         }
43865         me.showTrigger();
43866         available -= me.afterCt.getWidth();
43867
43868         // Hide all items which are off the end, and store them to allow them to be restored
43869         // before each layout operation.
43870         me.menuItems.length = 0;
43871         for (; i < len; i++) {
43872             box = boxes[i];
43873             if (box[startProp] + box[sizeProp] > available) {
43874                 me.menuItems.push(box.component);
43875                 box.component.hide();
43876             }
43877         }
43878     },
43879
43880     /**
43881      * @private
43882      * Creates the beforeCt, innerCt and afterCt elements if they have not already been created
43883      * @param {Ext.container.Container} container The Container attached to this Layout instance
43884      * @param {Ext.core.Element} target The target Element
43885      */
43886     createInnerElements: function() {
43887         var me = this,
43888             target = me.layout.getRenderTarget();
43889
43890         if (!this.afterCt) {
43891             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
43892             this.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + this.afterCtCls}, 'before');
43893         }
43894     },
43895
43896     /**
43897      * @private
43898      */
43899     destroy: function() {
43900         Ext.destroy(this.menu, this.menuTrigger);
43901     }
43902 });
43903 /**
43904  * @class Ext.util.Region
43905  * @extends Object
43906  *
43907  * Represents a rectangular region and provides a number of utility methods
43908  * to compare regions.
43909  */
43910
43911 Ext.define('Ext.util.Region', {
43912
43913     /* Begin Definitions */
43914
43915     requires: ['Ext.util.Offset'],
43916
43917     statics: {
43918         /**
43919          * @static
43920          * @param {Mixed} el A string, DomElement or Ext.core.Element representing an element
43921          * on the page.
43922          * @returns {Ext.util.Region} region
43923          * Retrieves an Ext.util.Region for a particular element.
43924          */
43925         getRegion: function(el) {
43926             return Ext.fly(el).getPageBox(true);
43927         },
43928
43929         /**
43930          * @static
43931          * @param {Object} o An object with top, right, bottom, left properties
43932          * @return {Ext.util.Region} region The region constructed based on the passed object
43933          */
43934         from: function(o) {
43935             return new this(o.top, o.right, o.bottom, o.left);
43936         }
43937     },
43938
43939     /* End Definitions */
43940
43941     /**
43942      * @constructor
43943      * @param {Number} top Top
43944      * @param {Number} right Right
43945      * @param {Number} bottom Bottom
43946      * @param {Number} left Left
43947      */
43948     constructor : function(t, r, b, l) {
43949         var me = this;
43950         me.y = me.top = me[1] = t;
43951         me.right = r;
43952         me.bottom = b;
43953         me.x = me.left = me[0] = l;
43954     },
43955
43956     /**
43957      * Checks if this region completely contains the region that is passed in.
43958      * @param {Ext.util.Region} region
43959      */
43960     contains : function(region) {
43961         var me = this;
43962         return (region.x >= me.x &&
43963                 region.right <= me.right &&
43964                 region.y >= me.y &&
43965                 region.bottom <= me.bottom);
43966
43967     },
43968
43969     /**
43970      * Checks if this region intersects the region passed in.
43971      * @param {Ext.util.Region} region
43972      * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection.
43973      */
43974     intersect : function(region) {
43975         var me = this,
43976             t = Math.max(me.y, region.y),
43977             r = Math.min(me.right, region.right),
43978             b = Math.min(me.bottom, region.bottom),
43979             l = Math.max(me.x, region.x);
43980
43981         if (b > t && r > l) {
43982             return new this.self(t, r, b, l);
43983         }
43984         else {
43985             return false;
43986         }
43987     },
43988
43989     /**
43990      * Returns the smallest region that contains the current AND targetRegion.
43991      * @param {Ext.util.Region} region
43992      */
43993     union : function(region) {
43994         var me = this,
43995             t = Math.min(me.y, region.y),
43996             r = Math.max(me.right, region.right),
43997             b = Math.max(me.bottom, region.bottom),
43998             l = Math.min(me.x, region.x);
43999
44000         return new this.self(t, r, b, l);
44001     },
44002
44003     /**
44004      * Modifies the current region to be constrained to the targetRegion.
44005      * @param {Ext.util.Region} targetRegion
44006      */
44007     constrainTo : function(r) {
44008         var me = this,
44009             constrain = Ext.Number.constrain;
44010         me.top = me.y = constrain(me.top, r.y, r.bottom);
44011         me.bottom = constrain(me.bottom, r.y, r.bottom);
44012         me.left = me.x = constrain(me.left, r.x, r.right);
44013         me.right = constrain(me.right, r.x, r.right);
44014         return me;
44015     },
44016
44017     /**
44018      * Modifies the current region to be adjusted by offsets.
44019      * @param {Number} top top offset
44020      * @param {Number} right right offset
44021      * @param {Number} bottom bottom offset
44022      * @param {Number} left left offset
44023      */
44024     adjust : function(t, r, b, l) {
44025         var me = this;
44026         me.top = me.y += t;
44027         me.left = me.x += l;
44028         me.right += r;
44029         me.bottom += b;
44030         return me;
44031     },
44032
44033     /**
44034      * Get the offset amount of a point outside the region
44035      * @param {String} axis optional
44036      * @param {Ext.util.Point} p the point
44037      * @return {Ext.util.Offset}
44038      */
44039     getOutOfBoundOffset: function(axis, p) {
44040         if (!Ext.isObject(axis)) {
44041             if (axis == 'x') {
44042                 return this.getOutOfBoundOffsetX(p);
44043             } else {
44044                 return this.getOutOfBoundOffsetY(p);
44045             }
44046         } else {
44047             p = axis;
44048             var d = Ext.create('Ext.util.Offset');
44049             d.x = this.getOutOfBoundOffsetX(p.x);
44050             d.y = this.getOutOfBoundOffsetY(p.y);
44051             return d;
44052         }
44053
44054     },
44055
44056     /**
44057      * Get the offset amount on the x-axis
44058      * @param {Number} p the offset
44059      * @return {Number}
44060      */
44061     getOutOfBoundOffsetX: function(p) {
44062         if (p <= this.x) {
44063             return this.x - p;
44064         } else if (p >= this.right) {
44065             return this.right - p;
44066         }
44067
44068         return 0;
44069     },
44070
44071     /**
44072      * Get the offset amount on the y-axis
44073      * @param {Number} p the offset
44074      * @return {Number}
44075      */
44076     getOutOfBoundOffsetY: function(p) {
44077         if (p <= this.y) {
44078             return this.y - p;
44079         } else if (p >= this.bottom) {
44080             return this.bottom - p;
44081         }
44082
44083         return 0;
44084     },
44085
44086     /**
44087      * Check whether the point / offset is out of bound
44088      * @param {String} axis optional
44089      * @param {Ext.util.Point/Number} p the point / offset
44090      * @return {Boolean}
44091      */
44092     isOutOfBound: function(axis, p) {
44093         if (!Ext.isObject(axis)) {
44094             if (axis == 'x') {
44095                 return this.isOutOfBoundX(p);
44096             } else {
44097                 return this.isOutOfBoundY(p);
44098             }
44099         } else {
44100             p = axis;
44101             return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
44102         }
44103     },
44104
44105     /**
44106      * Check whether the offset is out of bound in the x-axis
44107      * @param {Number} p the offset
44108      * @return {Boolean}
44109      */
44110     isOutOfBoundX: function(p) {
44111         return (p < this.x || p > this.right);
44112     },
44113
44114     /**
44115      * Check whether the offset is out of bound in the y-axis
44116      * @param {Number} p the offset
44117      * @return {Boolean}
44118      */
44119     isOutOfBoundY: function(p) {
44120         return (p < this.y || p > this.bottom);
44121     },
44122
44123     /*
44124      * Restrict a point within the region by a certain factor.
44125      * @param {String} axis Optional
44126      * @param {Ext.util.Point/Ext.util.Offset/Object} p
44127      * @param {Number} factor
44128      * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
44129      */
44130     restrict: function(axis, p, factor) {
44131         if (Ext.isObject(axis)) {
44132             var newP;
44133
44134             factor = p;
44135             p = axis;
44136
44137             if (p.copy) {
44138                 newP = p.copy();
44139             }
44140             else {
44141                 newP = {
44142                     x: p.x,
44143                     y: p.y
44144                 };
44145             }
44146
44147             newP.x = this.restrictX(p.x, factor);
44148             newP.y = this.restrictY(p.y, factor);
44149             return newP;
44150         } else {
44151             if (axis == 'x') {
44152                 return this.restrictX(p, factor);
44153             } else {
44154                 return this.restrictY(p, factor);
44155             }
44156         }
44157     },
44158
44159     /*
44160      * Restrict an offset within the region by a certain factor, on the x-axis
44161      * @param {Number} p
44162      * @param {Number} factor The factor, optional, defaults to 1
44163      * @return
44164      */
44165     restrictX : function(p, factor) {
44166         if (!factor) {
44167             factor = 1;
44168         }
44169
44170         if (p <= this.x) {
44171             p -= (p - this.x) * factor;
44172         }
44173         else if (p >= this.right) {
44174             p -= (p - this.right) * factor;
44175         }
44176         return p;
44177     },
44178
44179     /*
44180      * Restrict an offset within the region by a certain factor, on the y-axis
44181      * @param {Number} p
44182      * @param {Number} factor The factor, optional, defaults to 1
44183      */
44184     restrictY : function(p, factor) {
44185         if (!factor) {
44186             factor = 1;
44187         }
44188
44189         if (p <= this.y) {
44190             p -= (p - this.y) * factor;
44191         }
44192         else if (p >= this.bottom) {
44193             p -= (p - this.bottom) * factor;
44194         }
44195         return p;
44196     },
44197
44198     /*
44199      * Get the width / height of this region
44200      * @return {Object} an object with width and height properties
44201      */
44202     getSize: function() {
44203         return {
44204             width: this.right - this.x,
44205             height: this.bottom - this.y
44206         };
44207     },
44208
44209     /**
44210      * Copy a new instance
44211      * @return {Ext.util.Region}
44212      */
44213     copy: function() {
44214         return new this.self(this.y, this.right, this.bottom, this.x);
44215     },
44216
44217     /**
44218      * Copy the values of another Region to this Region
44219      * @param {Region} The region to copy from.
44220      * @return {Ext.util.Point} this This point
44221      */
44222     copyFrom: function(p) {
44223         var me = this;
44224         me.top = me.y = me[1] = p.y;
44225         me.right = p.right;
44226         me.bottom = p.bottom;
44227         me.left = me.x = me[0] = p.x;
44228
44229         return this;
44230     },
44231
44232     /**
44233      * Dump this to an eye-friendly string, great for debugging
44234      * @return {String}
44235      */
44236     toString: function() {
44237         return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
44238     },
44239
44240
44241     /**
44242      * Translate this region by the given offset amount
44243      * @param {Ext.util.Offset/Object} offset Object containing the <code>x</code> and <code>y</code> properties.
44244      * Or the x value is using the two argument form.
44245      * @param {Number} The y value unless using an Offset object.
44246      * @return {Ext.util.Region} this This Region
44247      */
44248     translateBy: function(x, y) {
44249         if (arguments.length == 1) {
44250             y = x.y;
44251             x = x.x;
44252         }
44253         var me = this;
44254         me.top = me.y += y;
44255         me.right += x;
44256         me.bottom += y;
44257         me.left = me.x += x;
44258
44259         return me;
44260     },
44261
44262     /**
44263      * Round all the properties of this region
44264      * @return {Ext.util.Region} this This Region
44265      */
44266     round: function() {
44267         var me = this;
44268         me.top = me.y = Math.round(me.y);
44269         me.right = Math.round(me.right);
44270         me.bottom = Math.round(me.bottom);
44271         me.left = me.x = Math.round(me.x);
44272
44273         return me;
44274     },
44275
44276     /**
44277      * Check whether this region is equivalent to the given region
44278      * @param {Ext.util.Region} region The region to compare with
44279      * @return {Boolean}
44280      */
44281     equals: function(region) {
44282         return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left);
44283     }
44284 });
44285
44286 /*
44287  * This is a derivative of the similarly named class in the YUI Library.
44288  * The original license:
44289  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
44290  * Code licensed under the BSD License:
44291  * http://developer.yahoo.net/yui/license.txt
44292  */
44293
44294
44295 /**
44296  * @class Ext.dd.DragDropManager
44297  * DragDropManager is a singleton that tracks the element interaction for
44298  * all DragDrop items in the window.  Generally, you will not call
44299  * this class directly, but it does have helper methods that could
44300  * be useful in your DragDrop implementations.
44301  * @singleton
44302  */
44303 Ext.define('Ext.dd.DragDropManager', {
44304     singleton: true,
44305
44306     requires: ['Ext.util.Region'],
44307
44308     uses: ['Ext.tip.QuickTipManager'],
44309
44310     // shorter ClassName, to save bytes and use internally
44311     alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
44312     
44313     /**
44314      * Two dimensional Array of registered DragDrop objects.  The first
44315      * dimension is the DragDrop item group, the second the DragDrop
44316      * object.
44317      * @property ids
44318      * @type String[]
44319      * @private
44320      * @static
44321      */
44322     ids: {},
44323
44324     /**
44325      * Array of element ids defined as drag handles.  Used to determine
44326      * if the element that generated the mousedown event is actually the
44327      * handle and not the html element itself.
44328      * @property handleIds
44329      * @type String[]
44330      * @private
44331      * @static
44332      */
44333     handleIds: {},
44334
44335     /**
44336      * the DragDrop object that is currently being dragged
44337      * @property dragCurrent
44338      * @type DragDrop
44339      * @private
44340      * @static
44341      **/
44342     dragCurrent: null,
44343
44344     /**
44345      * the DragDrop object(s) that are being hovered over
44346      * @property dragOvers
44347      * @type Array
44348      * @private
44349      * @static
44350      */
44351     dragOvers: {},
44352
44353     /**
44354      * the X distance between the cursor and the object being dragged
44355      * @property deltaX
44356      * @type int
44357      * @private
44358      * @static
44359      */
44360     deltaX: 0,
44361
44362     /**
44363      * the Y distance between the cursor and the object being dragged
44364      * @property deltaY
44365      * @type int
44366      * @private
44367      * @static
44368      */
44369     deltaY: 0,
44370
44371     /**
44372      * Flag to determine if we should prevent the default behavior of the
44373      * events we define. By default this is true, but this can be set to
44374      * false if you need the default behavior (not recommended)
44375      * @property preventDefault
44376      * @type boolean
44377      * @static
44378      */
44379     preventDefault: true,
44380
44381     /**
44382      * Flag to determine if we should stop the propagation of the events
44383      * we generate. This is true by default but you may want to set it to
44384      * false if the html element contains other features that require the
44385      * mouse click.
44386      * @property stopPropagation
44387      * @type boolean
44388      * @static
44389      */
44390     stopPropagation: true,
44391
44392     /**
44393      * Internal flag that is set to true when drag and drop has been
44394      * intialized
44395      * @property initialized
44396      * @private
44397      * @static
44398      */
44399     initialized: false,
44400
44401     /**
44402      * All drag and drop can be disabled.
44403      * @property locked
44404      * @private
44405      * @static
44406      */
44407     locked: false,
44408
44409     /**
44410      * Called the first time an element is registered.
44411      * @method init
44412      * @private
44413      * @static
44414      */
44415     init: function() {
44416         this.initialized = true;
44417     },
44418
44419     /**
44420      * In point mode, drag and drop interaction is defined by the
44421      * location of the cursor during the drag/drop
44422      * @property POINT
44423      * @type int
44424      * @static
44425      */
44426     POINT: 0,
44427
44428     /**
44429      * In intersect mode, drag and drop interaction is defined by the
44430      * overlap of two or more drag and drop objects.
44431      * @property INTERSECT
44432      * @type int
44433      * @static
44434      */
44435     INTERSECT: 1,
44436
44437     /**
44438      * The current drag and drop mode.  Default: POINT
44439      * @property mode
44440      * @type int
44441      * @static
44442      */
44443     mode: 0,
44444
44445     /**
44446      * Runs method on all drag and drop objects
44447      * @method _execOnAll
44448      * @private
44449      * @static
44450      */
44451     _execOnAll: function(sMethod, args) {
44452         for (var i in this.ids) {
44453             for (var j in this.ids[i]) {
44454                 var oDD = this.ids[i][j];
44455                 if (! this.isTypeOfDD(oDD)) {
44456                     continue;
44457                 }
44458                 oDD[sMethod].apply(oDD, args);
44459             }
44460         }
44461     },
44462
44463     /**
44464      * Drag and drop initialization.  Sets up the global event handlers
44465      * @method _onLoad
44466      * @private
44467      * @static
44468      */
44469     _onLoad: function() {
44470
44471         this.init();
44472
44473         var Event = Ext.EventManager;
44474         Event.on(document, "mouseup",   this.handleMouseUp, this, true);
44475         Event.on(document, "mousemove", this.handleMouseMove, this, true);
44476         Event.on(window,   "unload",    this._onUnload, this, true);
44477         Event.on(window,   "resize",    this._onResize, this, true);
44478         // Event.on(window,   "mouseout",    this._test);
44479
44480     },
44481
44482     /**
44483      * Reset constraints on all drag and drop objs
44484      * @method _onResize
44485      * @private
44486      * @static
44487      */
44488     _onResize: function(e) {
44489         this._execOnAll("resetConstraints", []);
44490     },
44491
44492     /**
44493      * Lock all drag and drop functionality
44494      * @method lock
44495      * @static
44496      */
44497     lock: function() { this.locked = true; },
44498
44499     /**
44500      * Unlock all drag and drop functionality
44501      * @method unlock
44502      * @static
44503      */
44504     unlock: function() { this.locked = false; },
44505
44506     /**
44507      * Is drag and drop locked?
44508      * @method isLocked
44509      * @return {boolean} True if drag and drop is locked, false otherwise.
44510      * @static
44511      */
44512     isLocked: function() { return this.locked; },
44513
44514     /**
44515      * Location cache that is set for all drag drop objects when a drag is
44516      * initiated, cleared when the drag is finished.
44517      * @property locationCache
44518      * @private
44519      * @static
44520      */
44521     locationCache: {},
44522
44523     /**
44524      * Set useCache to false if you want to force object the lookup of each
44525      * drag and drop linked element constantly during a drag.
44526      * @property useCache
44527      * @type boolean
44528      * @static
44529      */
44530     useCache: true,
44531
44532     /**
44533      * The number of pixels that the mouse needs to move after the
44534      * mousedown before the drag is initiated.  Default=3;
44535      * @property clickPixelThresh
44536      * @type int
44537      * @static
44538      */
44539     clickPixelThresh: 3,
44540
44541     /**
44542      * The number of milliseconds after the mousedown event to initiate the
44543      * drag if we don't get a mouseup event. Default=350
44544      * @property clickTimeThresh
44545      * @type int
44546      * @static
44547      */
44548     clickTimeThresh: 350,
44549
44550     /**
44551      * Flag that indicates that either the drag pixel threshold or the
44552      * mousdown time threshold has been met
44553      * @property dragThreshMet
44554      * @type boolean
44555      * @private
44556      * @static
44557      */
44558     dragThreshMet: false,
44559
44560     /**
44561      * Timeout used for the click time threshold
44562      * @property clickTimeout
44563      * @type Object
44564      * @private
44565      * @static
44566      */
44567     clickTimeout: null,
44568
44569     /**
44570      * The X position of the mousedown event stored for later use when a
44571      * drag threshold is met.
44572      * @property startX
44573      * @type int
44574      * @private
44575      * @static
44576      */
44577     startX: 0,
44578
44579     /**
44580      * The Y position of the mousedown event stored for later use when a
44581      * drag threshold is met.
44582      * @property startY
44583      * @type int
44584      * @private
44585      * @static
44586      */
44587     startY: 0,
44588
44589     /**
44590      * Each DragDrop instance must be registered with the DragDropManager.
44591      * This is executed in DragDrop.init()
44592      * @method regDragDrop
44593      * @param {DragDrop} oDD the DragDrop object to register
44594      * @param {String} sGroup the name of the group this element belongs to
44595      * @static
44596      */
44597     regDragDrop: function(oDD, sGroup) {
44598         if (!this.initialized) { this.init(); }
44599
44600         if (!this.ids[sGroup]) {
44601             this.ids[sGroup] = {};
44602         }
44603         this.ids[sGroup][oDD.id] = oDD;
44604     },
44605
44606     /**
44607      * Removes the supplied dd instance from the supplied group. Executed
44608      * by DragDrop.removeFromGroup, so don't call this function directly.
44609      * @method removeDDFromGroup
44610      * @private
44611      * @static
44612      */
44613     removeDDFromGroup: function(oDD, sGroup) {
44614         if (!this.ids[sGroup]) {
44615             this.ids[sGroup] = {};
44616         }
44617
44618         var obj = this.ids[sGroup];
44619         if (obj && obj[oDD.id]) {
44620             delete obj[oDD.id];
44621         }
44622     },
44623
44624     /**
44625      * Unregisters a drag and drop item.  This is executed in
44626      * DragDrop.unreg, use that method instead of calling this directly.
44627      * @method _remove
44628      * @private
44629      * @static
44630      */
44631     _remove: function(oDD) {
44632         for (var g in oDD.groups) {
44633             if (g && this.ids[g] && this.ids[g][oDD.id]) {
44634                 delete this.ids[g][oDD.id];
44635             }
44636         }
44637         delete this.handleIds[oDD.id];
44638     },
44639
44640     /**
44641      * Each DragDrop handle element must be registered.  This is done
44642      * automatically when executing DragDrop.setHandleElId()
44643      * @method regHandle
44644      * @param {String} sDDId the DragDrop id this element is a handle for
44645      * @param {String} sHandleId the id of the element that is the drag
44646      * handle
44647      * @static
44648      */
44649     regHandle: function(sDDId, sHandleId) {
44650         if (!this.handleIds[sDDId]) {
44651             this.handleIds[sDDId] = {};
44652         }
44653         this.handleIds[sDDId][sHandleId] = sHandleId;
44654     },
44655
44656     /**
44657      * Utility function to determine if a given element has been
44658      * registered as a drag drop item.
44659      * @method isDragDrop
44660      * @param {String} id the element id to check
44661      * @return {boolean} true if this element is a DragDrop item,
44662      * false otherwise
44663      * @static
44664      */
44665     isDragDrop: function(id) {
44666         return ( this.getDDById(id) ) ? true : false;
44667     },
44668
44669     /**
44670      * Returns the drag and drop instances that are in all groups the
44671      * passed in instance belongs to.
44672      * @method getRelated
44673      * @param {DragDrop} p_oDD the obj to get related data for
44674      * @param {boolean} bTargetsOnly if true, only return targetable objs
44675      * @return {DragDrop[]} the related instances
44676      * @static
44677      */
44678     getRelated: function(p_oDD, bTargetsOnly) {
44679         var oDDs = [];
44680         for (var i in p_oDD.groups) {
44681             for (var j in this.ids[i]) {
44682                 var dd = this.ids[i][j];
44683                 if (! this.isTypeOfDD(dd)) {
44684                     continue;
44685                 }
44686                 if (!bTargetsOnly || dd.isTarget) {
44687                     oDDs[oDDs.length] = dd;
44688                 }
44689             }
44690         }
44691
44692         return oDDs;
44693     },
44694
44695     /**
44696      * Returns true if the specified dd target is a legal target for
44697      * the specifice drag obj
44698      * @method isLegalTarget
44699      * @param {DragDrop} oDD the drag obj
44700      * @param {DragDrop} oTargetDD the target
44701      * @return {boolean} true if the target is a legal target for the
44702      * dd obj
44703      * @static
44704      */
44705     isLegalTarget: function (oDD, oTargetDD) {
44706         var targets = this.getRelated(oDD, true);
44707         for (var i=0, len=targets.length;i<len;++i) {
44708             if (targets[i].id == oTargetDD.id) {
44709                 return true;
44710             }
44711         }
44712
44713         return false;
44714     },
44715
44716     /**
44717      * My goal is to be able to transparently determine if an object is
44718      * typeof DragDrop, and the exact subclass of DragDrop.  typeof
44719      * returns "object", oDD.constructor.toString() always returns
44720      * "DragDrop" and not the name of the subclass.  So for now it just
44721      * evaluates a well-known variable in DragDrop.
44722      * @method isTypeOfDD
44723      * @param {Object} the object to evaluate
44724      * @return {boolean} true if typeof oDD = DragDrop
44725      * @static
44726      */
44727     isTypeOfDD: function (oDD) {
44728         return (oDD && oDD.__ygDragDrop);
44729     },
44730
44731     /**
44732      * Utility function to determine if a given element has been
44733      * registered as a drag drop handle for the given Drag Drop object.
44734      * @method isHandle
44735      * @param {String} id the element id to check
44736      * @return {boolean} true if this element is a DragDrop handle, false
44737      * otherwise
44738      * @static
44739      */
44740     isHandle: function(sDDId, sHandleId) {
44741         return ( this.handleIds[sDDId] &&
44742                         this.handleIds[sDDId][sHandleId] );
44743     },
44744
44745     /**
44746      * Returns the DragDrop instance for a given id
44747      * @method getDDById
44748      * @param {String} id the id of the DragDrop object
44749      * @return {DragDrop} the drag drop object, null if it is not found
44750      * @static
44751      */
44752     getDDById: function(id) {
44753         for (var i in this.ids) {
44754             if (this.ids[i][id]) {
44755                 return this.ids[i][id];
44756             }
44757         }
44758         return null;
44759     },
44760
44761     /**
44762      * Fired after a registered DragDrop object gets the mousedown event.
44763      * Sets up the events required to track the object being dragged
44764      * @method handleMouseDown
44765      * @param {Event} e the event
44766      * @param oDD the DragDrop object being dragged
44767      * @private
44768      * @static
44769      */
44770     handleMouseDown: function(e, oDD) {
44771         if(Ext.tip.QuickTipManager){
44772             Ext.tip.QuickTipManager.ddDisable();
44773         }
44774         if(this.dragCurrent){
44775             // the original browser mouseup wasn't handled (e.g. outside FF browser window)
44776             // so clean up first to avoid breaking the next drag
44777             this.handleMouseUp(e);
44778         }
44779         
44780         this.currentTarget = e.getTarget();
44781         this.dragCurrent = oDD;
44782
44783         var el = oDD.getEl();
44784
44785         // track start position
44786         this.startX = e.getPageX();
44787         this.startY = e.getPageY();
44788
44789         this.deltaX = this.startX - el.offsetLeft;
44790         this.deltaY = this.startY - el.offsetTop;
44791
44792         this.dragThreshMet = false;
44793
44794         this.clickTimeout = setTimeout(
44795                 function() {
44796                     var DDM = Ext.dd.DragDropManager;
44797                     DDM.startDrag(DDM.startX, DDM.startY);
44798                 },
44799                 this.clickTimeThresh );
44800     },
44801
44802     /**
44803      * Fired when either the drag pixel threshol or the mousedown hold
44804      * time threshold has been met.
44805      * @method startDrag
44806      * @param x {int} the X position of the original mousedown
44807      * @param y {int} the Y position of the original mousedown
44808      * @static
44809      */
44810     startDrag: function(x, y) {
44811         clearTimeout(this.clickTimeout);
44812         if (this.dragCurrent) {
44813             this.dragCurrent.b4StartDrag(x, y);
44814             this.dragCurrent.startDrag(x, y);
44815         }
44816         this.dragThreshMet = true;
44817     },
44818
44819     /**
44820      * Internal function to handle the mouseup event.  Will be invoked
44821      * from the context of the document.
44822      * @method handleMouseUp
44823      * @param {Event} e the event
44824      * @private
44825      * @static
44826      */
44827     handleMouseUp: function(e) {
44828
44829         if(Ext.tip.QuickTipManager){
44830             Ext.tip.QuickTipManager.ddEnable();
44831         }
44832         if (! this.dragCurrent) {
44833             return;
44834         }
44835
44836         clearTimeout(this.clickTimeout);
44837
44838         if (this.dragThreshMet) {
44839             this.fireEvents(e, true);
44840         } else {
44841         }
44842
44843         this.stopDrag(e);
44844
44845         this.stopEvent(e);
44846     },
44847
44848     /**
44849      * Utility to stop event propagation and event default, if these
44850      * features are turned on.
44851      * @method stopEvent
44852      * @param {Event} e the event as returned by this.getEvent()
44853      * @static
44854      */
44855     stopEvent: function(e){
44856         if(this.stopPropagation) {
44857             e.stopPropagation();
44858         }
44859
44860         if (this.preventDefault) {
44861             e.preventDefault();
44862         }
44863     },
44864
44865     /**
44866      * Internal function to clean up event handlers after the drag
44867      * operation is complete
44868      * @method stopDrag
44869      * @param {Event} e the event
44870      * @private
44871      * @static
44872      */
44873     stopDrag: function(e) {
44874         // Fire the drag end event for the item that was dragged
44875         if (this.dragCurrent) {
44876             if (this.dragThreshMet) {
44877                 this.dragCurrent.b4EndDrag(e);
44878                 this.dragCurrent.endDrag(e);
44879             }
44880
44881             this.dragCurrent.onMouseUp(e);
44882         }
44883
44884         this.dragCurrent = null;
44885         this.dragOvers = {};
44886     },
44887
44888     /**
44889      * Internal function to handle the mousemove event.  Will be invoked
44890      * from the context of the html element.
44891      *
44892      * @TODO figure out what we can do about mouse events lost when the
44893      * user drags objects beyond the window boundary.  Currently we can
44894      * detect this in internet explorer by verifying that the mouse is
44895      * down during the mousemove event.  Firefox doesn't give us the
44896      * button state on the mousemove event.
44897      * @method handleMouseMove
44898      * @param {Event} e the event
44899      * @private
44900      * @static
44901      */
44902     handleMouseMove: function(e) {
44903         if (! this.dragCurrent) {
44904             return true;
44905         }
44906         // var button = e.which || e.button;
44907
44908         // check for IE mouseup outside of page boundary
44909         if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
44910             this.stopEvent(e);
44911             return this.handleMouseUp(e);
44912         }
44913
44914         if (!this.dragThreshMet) {
44915             var diffX = Math.abs(this.startX - e.getPageX());
44916             var diffY = Math.abs(this.startY - e.getPageY());
44917             if (diffX > this.clickPixelThresh ||
44918                         diffY > this.clickPixelThresh) {
44919                 this.startDrag(this.startX, this.startY);
44920             }
44921         }
44922
44923         if (this.dragThreshMet) {
44924             this.dragCurrent.b4Drag(e);
44925             this.dragCurrent.onDrag(e);
44926             if(!this.dragCurrent.moveOnly){
44927                 this.fireEvents(e, false);
44928             }
44929         }
44930
44931         this.stopEvent(e);
44932
44933         return true;
44934     },
44935
44936     /**
44937      * Iterates over all of the DragDrop elements to find ones we are
44938      * hovering over or dropping on
44939      * @method fireEvents
44940      * @param {Event} e the event
44941      * @param {boolean} isDrop is this a drop op or a mouseover op?
44942      * @private
44943      * @static
44944      */
44945     fireEvents: function(e, isDrop) {
44946         var dc = this.dragCurrent;
44947
44948         // If the user did the mouse up outside of the window, we could
44949         // get here even though we have ended the drag.
44950         if (!dc || dc.isLocked()) {
44951             return;
44952         }
44953
44954         var pt = e.getPoint();
44955
44956         // cache the previous dragOver array
44957         var oldOvers = [];
44958
44959         var outEvts   = [];
44960         var overEvts  = [];
44961         var dropEvts  = [];
44962         var enterEvts = [];
44963
44964         // Check to see if the object(s) we were hovering over is no longer
44965         // being hovered over so we can fire the onDragOut event
44966         for (var i in this.dragOvers) {
44967
44968             var ddo = this.dragOvers[i];
44969
44970             if (! this.isTypeOfDD(ddo)) {
44971                 continue;
44972             }
44973
44974             if (! this.isOverTarget(pt, ddo, this.mode)) {
44975                 outEvts.push( ddo );
44976             }
44977
44978             oldOvers[i] = true;
44979             delete this.dragOvers[i];
44980         }
44981
44982         for (var sGroup in dc.groups) {
44983
44984             if ("string" != typeof sGroup) {
44985                 continue;
44986             }
44987
44988             for (i in this.ids[sGroup]) {
44989                 var oDD = this.ids[sGroup][i];
44990                 if (! this.isTypeOfDD(oDD)) {
44991                     continue;
44992                 }
44993
44994                 if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
44995                     if (this.isOverTarget(pt, oDD, this.mode)) {
44996                         // look for drop interactions
44997                         if (isDrop) {
44998                             dropEvts.push( oDD );
44999                         // look for drag enter and drag over interactions
45000                         } else {
45001
45002                             // initial drag over: dragEnter fires
45003                             if (!oldOvers[oDD.id]) {
45004                                 enterEvts.push( oDD );
45005                             // subsequent drag overs: dragOver fires
45006                             } else {
45007                                 overEvts.push( oDD );
45008                             }
45009
45010                             this.dragOvers[oDD.id] = oDD;
45011                         }
45012                     }
45013                 }
45014             }
45015         }
45016
45017         if (this.mode) {
45018             if (outEvts.length) {
45019                 dc.b4DragOut(e, outEvts);
45020                 dc.onDragOut(e, outEvts);
45021             }
45022
45023             if (enterEvts.length) {
45024                 dc.onDragEnter(e, enterEvts);
45025             }
45026
45027             if (overEvts.length) {
45028                 dc.b4DragOver(e, overEvts);
45029                 dc.onDragOver(e, overEvts);
45030             }
45031
45032             if (dropEvts.length) {
45033                 dc.b4DragDrop(e, dropEvts);
45034                 dc.onDragDrop(e, dropEvts);
45035             }
45036
45037         } else {
45038             // fire dragout events
45039             var len = 0;
45040             for (i=0, len=outEvts.length; i<len; ++i) {
45041                 dc.b4DragOut(e, outEvts[i].id);
45042                 dc.onDragOut(e, outEvts[i].id);
45043             }
45044
45045             // fire enter events
45046             for (i=0,len=enterEvts.length; i<len; ++i) {
45047                 // dc.b4DragEnter(e, oDD.id);
45048                 dc.onDragEnter(e, enterEvts[i].id);
45049             }
45050
45051             // fire over events
45052             for (i=0,len=overEvts.length; i<len; ++i) {
45053                 dc.b4DragOver(e, overEvts[i].id);
45054                 dc.onDragOver(e, overEvts[i].id);
45055             }
45056
45057             // fire drop events
45058             for (i=0, len=dropEvts.length; i<len; ++i) {
45059                 dc.b4DragDrop(e, dropEvts[i].id);
45060                 dc.onDragDrop(e, dropEvts[i].id);
45061             }
45062
45063         }
45064
45065         // notify about a drop that did not find a target
45066         if (isDrop && !dropEvts.length) {
45067             dc.onInvalidDrop(e);
45068         }
45069
45070     },
45071
45072     /**
45073      * Helper function for getting the best match from the list of drag
45074      * and drop objects returned by the drag and drop events when we are
45075      * in INTERSECT mode.  It returns either the first object that the
45076      * cursor is over, or the object that has the greatest overlap with
45077      * the dragged element.
45078      * @method getBestMatch
45079      * @param  {DragDrop[]} dds The array of drag and drop objects
45080      * targeted
45081      * @return {DragDrop}       The best single match
45082      * @static
45083      */
45084     getBestMatch: function(dds) {
45085         var winner = null;
45086         // Return null if the input is not what we expect
45087         //if (!dds || !dds.length || dds.length == 0) {
45088            // winner = null;
45089         // If there is only one item, it wins
45090         //} else if (dds.length == 1) {
45091
45092         var len = dds.length;
45093
45094         if (len == 1) {
45095             winner = dds[0];
45096         } else {
45097             // Loop through the targeted items
45098             for (var i=0; i<len; ++i) {
45099                 var dd = dds[i];
45100                 // If the cursor is over the object, it wins.  If the
45101                 // cursor is over multiple matches, the first one we come
45102                 // to wins.
45103                 if (dd.cursorIsOver) {
45104                     winner = dd;
45105                     break;
45106                 // Otherwise the object with the most overlap wins
45107                 } else {
45108                     if (!winner ||
45109                         winner.overlap.getArea() < dd.overlap.getArea()) {
45110                         winner = dd;
45111                     }
45112                 }
45113             }
45114         }
45115
45116         return winner;
45117     },
45118
45119     /**
45120      * Refreshes the cache of the top-left and bottom-right points of the
45121      * drag and drop objects in the specified group(s).  This is in the
45122      * format that is stored in the drag and drop instance, so typical
45123      * usage is:
45124      * <code>
45125      * Ext.dd.DragDropManager.refreshCache(ddinstance.groups);
45126      * </code>
45127      * Alternatively:
45128      * <code>
45129      * Ext.dd.DragDropManager.refreshCache({group1:true, group2:true});
45130      * </code>
45131      * @TODO this really should be an indexed array.  Alternatively this
45132      * method could accept both.
45133      * @method refreshCache
45134      * @param {Object} groups an associative array of groups to refresh
45135      * @static
45136      */
45137     refreshCache: function(groups) {
45138         for (var sGroup in groups) {
45139             if ("string" != typeof sGroup) {
45140                 continue;
45141             }
45142             for (var i in this.ids[sGroup]) {
45143                 var oDD = this.ids[sGroup][i];
45144
45145                 if (this.isTypeOfDD(oDD)) {
45146                 // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
45147                     var loc = this.getLocation(oDD);
45148                     if (loc) {
45149                         this.locationCache[oDD.id] = loc;
45150                     } else {
45151                         delete this.locationCache[oDD.id];
45152                         // this will unregister the drag and drop object if
45153                         // the element is not in a usable state
45154                         // oDD.unreg();
45155                     }
45156                 }
45157             }
45158         }
45159     },
45160
45161     /**
45162      * This checks to make sure an element exists and is in the DOM.  The
45163      * main purpose is to handle cases where innerHTML is used to remove
45164      * drag and drop objects from the DOM.  IE provides an 'unspecified
45165      * error' when trying to access the offsetParent of such an element
45166      * @method verifyEl
45167      * @param {HTMLElement} el the element to check
45168      * @return {boolean} true if the element looks usable
45169      * @static
45170      */
45171     verifyEl: function(el) {
45172         if (el) {
45173             var parent;
45174             if(Ext.isIE){
45175                 try{
45176                     parent = el.offsetParent;
45177                 }catch(e){}
45178             }else{
45179                 parent = el.offsetParent;
45180             }
45181             if (parent) {
45182                 return true;
45183             }
45184         }
45185
45186         return false;
45187     },
45188
45189     /**
45190      * Returns a Region object containing the drag and drop element's position
45191      * and size, including the padding configured for it
45192      * @method getLocation
45193      * @param {DragDrop} oDD the drag and drop object to get the
45194      *                       location for
45195      * @return {Ext.util.Region} a Region object representing the total area
45196      *                             the element occupies, including any padding
45197      *                             the instance is configured for.
45198      * @static
45199      */
45200     getLocation: function(oDD) {
45201         if (! this.isTypeOfDD(oDD)) {
45202             return null;
45203         }
45204
45205         //delegate getLocation method to the
45206         //drag and drop target.
45207         if (oDD.getRegion) {
45208             return oDD.getRegion();
45209         }
45210
45211         var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
45212
45213         try {
45214             pos= Ext.core.Element.getXY(el);
45215         } catch (e) { }
45216
45217         if (!pos) {
45218             return null;
45219         }
45220
45221         x1 = pos[0];
45222         x2 = x1 + el.offsetWidth;
45223         y1 = pos[1];
45224         y2 = y1 + el.offsetHeight;
45225
45226         t = y1 - oDD.padding[0];
45227         r = x2 + oDD.padding[1];
45228         b = y2 + oDD.padding[2];
45229         l = x1 - oDD.padding[3];
45230
45231         return Ext.create('Ext.util.Region', t, r, b, l);
45232     },
45233
45234     /**
45235      * Checks the cursor location to see if it over the target
45236      * @method isOverTarget
45237      * @param {Ext.util.Point} pt The point to evaluate
45238      * @param {DragDrop} oTarget the DragDrop object we are inspecting
45239      * @return {boolean} true if the mouse is over the target
45240      * @private
45241      * @static
45242      */
45243     isOverTarget: function(pt, oTarget, intersect) {
45244         // use cache if available
45245         var loc = this.locationCache[oTarget.id];
45246         if (!loc || !this.useCache) {
45247             loc = this.getLocation(oTarget);
45248             this.locationCache[oTarget.id] = loc;
45249
45250         }
45251
45252         if (!loc) {
45253             return false;
45254         }
45255
45256         oTarget.cursorIsOver = loc.contains( pt );
45257
45258         // DragDrop is using this as a sanity check for the initial mousedown
45259         // in this case we are done.  In POINT mode, if the drag obj has no
45260         // contraints, we are also done. Otherwise we need to evaluate the
45261         // location of the target as related to the actual location of the
45262         // dragged element.
45263         var dc = this.dragCurrent;
45264         if (!dc || !dc.getTargetCoord ||
45265                 (!intersect && !dc.constrainX && !dc.constrainY)) {
45266             return oTarget.cursorIsOver;
45267         }
45268
45269         oTarget.overlap = null;
45270
45271         // Get the current location of the drag element, this is the
45272         // location of the mouse event less the delta that represents
45273         // where the original mousedown happened on the element.  We
45274         // need to consider constraints and ticks as well.
45275         var pos = dc.getTargetCoord(pt.x, pt.y);
45276
45277         var el = dc.getDragEl();
45278         var curRegion = Ext.create('Ext.util.Region', pos.y,
45279                                                pos.x + el.offsetWidth,
45280                                                pos.y + el.offsetHeight,
45281                                                pos.x );
45282
45283         var overlap = curRegion.intersect(loc);
45284
45285         if (overlap) {
45286             oTarget.overlap = overlap;
45287             return (intersect) ? true : oTarget.cursorIsOver;
45288         } else {
45289             return false;
45290         }
45291     },
45292
45293     /**
45294      * unload event handler
45295      * @method _onUnload
45296      * @private
45297      * @static
45298      */
45299     _onUnload: function(e, me) {
45300         Ext.dd.DragDropManager.unregAll();
45301     },
45302
45303     /**
45304      * Cleans up the drag and drop events and objects.
45305      * @method unregAll
45306      * @private
45307      * @static
45308      */
45309     unregAll: function() {
45310
45311         if (this.dragCurrent) {
45312             this.stopDrag();
45313             this.dragCurrent = null;
45314         }
45315
45316         this._execOnAll("unreg", []);
45317
45318         for (var i in this.elementCache) {
45319             delete this.elementCache[i];
45320         }
45321
45322         this.elementCache = {};
45323         this.ids = {};
45324     },
45325
45326     /**
45327      * A cache of DOM elements
45328      * @property elementCache
45329      * @private
45330      * @static
45331      */
45332     elementCache: {},
45333
45334     /**
45335      * Get the wrapper for the DOM element specified
45336      * @method getElWrapper
45337      * @param {String} id the id of the element to get
45338      * @return {Ext.dd.DDM.ElementWrapper} the wrapped element
45339      * @private
45340      * @deprecated This wrapper isn't that useful
45341      * @static
45342      */
45343     getElWrapper: function(id) {
45344         var oWrapper = this.elementCache[id];
45345         if (!oWrapper || !oWrapper.el) {
45346             oWrapper = this.elementCache[id] =
45347                 new this.ElementWrapper(Ext.getDom(id));
45348         }
45349         return oWrapper;
45350     },
45351
45352     /**
45353      * Returns the actual DOM element
45354      * @method getElement
45355      * @param {String} id the id of the elment to get
45356      * @return {Object} The element
45357      * @deprecated use Ext.lib.Ext.getDom instead
45358      * @static
45359      */
45360     getElement: function(id) {
45361         return Ext.getDom(id);
45362     },
45363
45364     /**
45365      * Returns the style property for the DOM element (i.e.,
45366      * document.getElById(id).style)
45367      * @method getCss
45368      * @param {String} id the id of the elment to get
45369      * @return {Object} The style property of the element
45370      * @static
45371      */
45372     getCss: function(id) {
45373         var el = Ext.getDom(id);
45374         return (el) ? el.style : null;
45375     },
45376
45377     /**
45378      * Inner class for cached elements
45379      * @class Ext.dd.DragDropManager.ElementWrapper
45380      * @for DragDropManager
45381      * @private
45382      * @deprecated
45383      */
45384     ElementWrapper: function(el) {
45385             /**
45386              * The element
45387              * @property el
45388              */
45389             this.el = el || null;
45390             /**
45391              * The element id
45392              * @property id
45393              */
45394             this.id = this.el && el.id;
45395             /**
45396              * A reference to the style property
45397              * @property css
45398              */
45399             this.css = this.el && el.style;
45400         },
45401
45402     /**
45403      * Returns the X position of an html element
45404      * @method getPosX
45405      * @param el the element for which to get the position
45406      * @return {int} the X coordinate
45407      * @for DragDropManager
45408      * @static
45409      */
45410     getPosX: function(el) {
45411         return Ext.core.Element.getX(el);
45412     },
45413
45414     /**
45415      * Returns the Y position of an html element
45416      * @method getPosY
45417      * @param el the element for which to get the position
45418      * @return {int} the Y coordinate
45419      * @static
45420      */
45421     getPosY: function(el) {
45422         return Ext.core.Element.getY(el);
45423     },
45424
45425     /**
45426      * Swap two nodes.  In IE, we use the native method, for others we
45427      * emulate the IE behavior
45428      * @method swapNode
45429      * @param n1 the first node to swap
45430      * @param n2 the other node to swap
45431      * @static
45432      */
45433     swapNode: function(n1, n2) {
45434         if (n1.swapNode) {
45435             n1.swapNode(n2);
45436         } else {
45437             var p = n2.parentNode;
45438             var s = n2.nextSibling;
45439
45440             if (s == n1) {
45441                 p.insertBefore(n1, n2);
45442             } else if (n2 == n1.nextSibling) {
45443                 p.insertBefore(n2, n1);
45444             } else {
45445                 n1.parentNode.replaceChild(n2, n1);
45446                 p.insertBefore(n1, s);
45447             }
45448         }
45449     },
45450
45451     /**
45452      * Returns the current scroll position
45453      * @method getScroll
45454      * @private
45455      * @static
45456      */
45457     getScroll: function () {
45458         var doc   = window.document,
45459             docEl = doc.documentElement,
45460             body  = doc.body,
45461             top   = 0,
45462             left  = 0;
45463             
45464         if (Ext.isGecko4) {
45465             top  = window.scrollYOffset;
45466             left = window.scrollXOffset;
45467         } else {
45468             if (docEl && (docEl.scrollTop || docEl.scrollLeft)) {
45469                 top  = docEl.scrollTop;
45470                 left = docEl.scrollLeft;
45471             } else if (body) {
45472                 top  = body.scrollTop;
45473                 left = body.scrollLeft;
45474             } 
45475         }
45476         return {
45477             top: top,
45478             left: left
45479         };
45480     },
45481
45482     /**
45483      * Returns the specified element style property
45484      * @method getStyle
45485      * @param {HTMLElement} el          the element
45486      * @param {string}      styleProp   the style property
45487      * @return {string} The value of the style property
45488      * @static
45489      */
45490     getStyle: function(el, styleProp) {
45491         return Ext.fly(el).getStyle(styleProp);
45492     },
45493
45494     /**
45495      * Gets the scrollTop
45496      * @method getScrollTop
45497      * @return {int} the document's scrollTop
45498      * @static
45499      */
45500     getScrollTop: function () {
45501         return this.getScroll().top;
45502     },
45503
45504     /**
45505      * Gets the scrollLeft
45506      * @method getScrollLeft
45507      * @return {int} the document's scrollTop
45508      * @static
45509      */
45510     getScrollLeft: function () {
45511         return this.getScroll().left;
45512     },
45513
45514     /**
45515      * Sets the x/y position of an element to the location of the
45516      * target element.
45517      * @method moveToEl
45518      * @param {HTMLElement} moveEl      The element to move
45519      * @param {HTMLElement} targetEl    The position reference element
45520      * @static
45521      */
45522     moveToEl: function (moveEl, targetEl) {
45523         var aCoord = Ext.core.Element.getXY(targetEl);
45524         Ext.core.Element.setXY(moveEl, aCoord);
45525     },
45526
45527     /**
45528      * Numeric array sort function
45529      * @method numericSort
45530      * @static
45531      */
45532     numericSort: function(a, b) {
45533         return (a - b);
45534     },
45535
45536     /**
45537      * Internal counter
45538      * @property _timeoutCount
45539      * @private
45540      * @static
45541      */
45542     _timeoutCount: 0,
45543
45544     /**
45545      * Trying to make the load order less important.  Without this we get
45546      * an error if this file is loaded before the Event Utility.
45547      * @method _addListeners
45548      * @private
45549      * @static
45550      */
45551     _addListeners: function() {
45552         if ( document ) {
45553             this._onLoad();
45554         } else {
45555             if (this._timeoutCount > 2000) {
45556             } else {
45557                 setTimeout(this._addListeners, 10);
45558                 if (document && document.body) {
45559                     this._timeoutCount += 1;
45560                 }
45561             }
45562         }
45563     },
45564
45565     /**
45566      * Recursively searches the immediate parent and all child nodes for
45567      * the handle element in order to determine wheter or not it was
45568      * clicked.
45569      * @method handleWasClicked
45570      * @param node the html element to inspect
45571      * @static
45572      */
45573     handleWasClicked: function(node, id) {
45574         if (this.isHandle(id, node.id)) {
45575             return true;
45576         } else {
45577             // check to see if this is a text node child of the one we want
45578             var p = node.parentNode;
45579
45580             while (p) {
45581                 if (this.isHandle(id, p.id)) {
45582                     return true;
45583                 } else {
45584                     p = p.parentNode;
45585                 }
45586             }
45587         }
45588
45589         return false;
45590     }
45591 }, function() {
45592     this._addListeners();
45593 });
45594
45595 /**
45596  * @class Ext.layout.container.Box
45597  * @extends Ext.layout.container.Container
45598  * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
45599  */
45600
45601 Ext.define('Ext.layout.container.Box', {
45602
45603     /* Begin Definitions */
45604
45605     alias: ['layout.box'],
45606     extend: 'Ext.layout.container.Container',
45607     alternateClassName: 'Ext.layout.BoxLayout',
45608     
45609     requires: [
45610         'Ext.layout.container.boxOverflow.None',
45611         'Ext.layout.container.boxOverflow.Menu',
45612         'Ext.layout.container.boxOverflow.Scroller',
45613         'Ext.util.Format',
45614         'Ext.dd.DragDropManager'
45615     ],
45616
45617     /* End Definitions */
45618
45619     /**
45620      * @cfg {Mixed} animate
45621      * <p>If truthy, child Component are <i>animated</i> into position whenever the Container
45622      * is layed out. If this option is numeric, it is used as the animation duration in milliseconds.</p>
45623      * <p>May be set as a property at any time.</p>
45624      */
45625
45626     /**
45627      * @cfg {Object} defaultMargins
45628      * <p>If the individual contained items do not have a <tt>margins</tt>
45629      * property specified or margin specified via CSS, the default margins from this property will be
45630      * applied to each item.</p>
45631      * <br><p>This property may be specified as an object containing margins
45632      * to apply in the format:</p><pre><code>
45633 {
45634     top: (top margin),
45635     right: (right margin),
45636     bottom: (bottom margin),
45637     left: (left margin)
45638 }</code></pre>
45639      * <p>This property may also be specified as a string containing
45640      * space-separated, numeric margin values. The order of the sides associated
45641      * with each value matches the way CSS processes margin values:</p>
45642      * <div class="mdetail-params"><ul>
45643      * <li>If there is only one value, it applies to all sides.</li>
45644      * <li>If there are two values, the top and bottom borders are set to the
45645      * first value and the right and left are set to the second.</li>
45646      * <li>If there are three values, the top is set to the first value, the left
45647      * and right are set to the second, and the bottom is set to the third.</li>
45648      * <li>If there are four values, they apply to the top, right, bottom, and
45649      * left, respectively.</li>
45650      * </ul></div>
45651      * <p>Defaults to:</p><pre><code>
45652      * {top:0, right:0, bottom:0, left:0}
45653      * </code></pre>
45654      */
45655     defaultMargins: {
45656         top: 0,
45657         right: 0,
45658         bottom: 0,
45659         left: 0
45660     },
45661
45662     /**
45663      * @cfg {String} padding
45664      * <p>Sets the padding to be applied to all child items managed by this layout.</p>
45665      * <p>This property must be specified as a string containing
45666      * space-separated, numeric padding values. The order of the sides associated
45667      * with each value matches the way CSS processes padding values:</p>
45668      * <div class="mdetail-params"><ul>
45669      * <li>If there is only one value, it applies to all sides.</li>
45670      * <li>If there are two values, the top and bottom borders are set to the
45671      * first value and the right and left are set to the second.</li>
45672      * <li>If there are three values, the top is set to the first value, the left
45673      * and right are set to the second, and the bottom is set to the third.</li>
45674      * <li>If there are four values, they apply to the top, right, bottom, and
45675      * left, respectively.</li>
45676      * </ul></div>
45677      * <p>Defaults to: <code>"0"</code></p>
45678      */
45679     padding: '0',
45680     // documented in subclasses
45681     pack: 'start',
45682
45683     /**
45684      * @cfg {String} pack
45685      * Controls how the child items of the container are packed together. Acceptable configuration values
45686      * for this property are:
45687      * <div class="mdetail-params"><ul>
45688      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
45689      * <b>left</b> side of container</div></li>
45690      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
45691      * <b>mid-width</b> of container</div></li>
45692      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
45693      * side of container</div></li>
45694      * </ul></div>
45695      */
45696     /**
45697      * @cfg {Number} flex
45698      * This configuration option is to be applied to <b>child <tt>items</tt></b> of the container managed
45699      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
45700      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
45701      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
45702      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
45703      */
45704
45705     type: 'box',
45706     scrollOffset: 0,
45707     itemCls: Ext.baseCSSPrefix + 'box-item',
45708     targetCls: Ext.baseCSSPrefix + 'box-layout-ct',
45709     innerCls: Ext.baseCSSPrefix + 'box-inner',
45710
45711     bindToOwnerCtContainer: true,
45712
45713     fixedLayout: false,
45714     
45715     // availableSpaceOffset is used to adjust the availableWidth, typically used
45716     // to reserve space for a scrollbar
45717     availableSpaceOffset: 0,
45718     
45719     // whether or not to reserve the availableSpaceOffset in layout calculations
45720     reserveOffset: true,
45721     
45722     /**
45723      * @cfg {Boolean} clearInnerCtOnLayout
45724      */
45725     clearInnerCtOnLayout: false,
45726
45727     flexSortFn: function (a, b) {
45728         var maxParallelPrefix = 'max' + this.parallelPrefixCap,
45729             infiniteValue = Infinity;
45730         a = a.component[maxParallelPrefix] || infiniteValue;
45731         b = b.component[maxParallelPrefix] || infiniteValue;
45732         // IE 6/7 Don't like Infinity - Infinity...
45733         if (!isFinite(a) && !isFinite(b)) {
45734             return false;
45735         }
45736         return a - b;
45737     },
45738
45739     // Sort into *descending* order.
45740     minSizeSortFn: function(a, b) {
45741         return b.available - a.available;
45742     },
45743
45744     constructor: function(config) {
45745         var me = this;
45746
45747         me.callParent(arguments);
45748
45749         // The sort function needs access to properties in this, so must be bound.
45750         me.flexSortFn = Ext.Function.bind(me.flexSortFn, me);
45751
45752         me.initOverflowHandler();
45753     },
45754
45755     /**
45756      * @private
45757      * Returns the current size and positioning of the passed child item.
45758      * @param {Component} child The child Component to calculate the box for
45759      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
45760      */
45761     getChildBox: function(child) {
45762         child = child.el || this.owner.getComponent(child).el;
45763         return {
45764             left: child.getLeft(true),
45765             top: child.getTop(true),
45766             width: child.getWidth(),
45767             height: child.getHeight()
45768         };
45769     },
45770
45771     /**
45772      * @private
45773      * Calculates the size and positioning of the passed child item.
45774      * @param {Component} child The child Component to calculate the box for
45775      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
45776      */
45777     calculateChildBox: function(child) {
45778         var me = this,
45779             boxes = me.calculateChildBoxes(me.getVisibleItems(), me.getLayoutTargetSize()).boxes,
45780             ln = boxes.length,
45781             i = 0;
45782
45783         child = me.owner.getComponent(child);
45784         for (; i < ln; i++) {
45785             if (boxes[i].component === child) {
45786                 return boxes[i];
45787             }
45788         }
45789     },
45790
45791     /**
45792      * @private
45793      * Calculates the size and positioning of each item in the box. This iterates over all of the rendered,
45794      * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
45795      * returns meta data such as maxSize which are useful when resizing layout wrappers such as this.innerCt.
45796      * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
45797      * @param {Object} targetSize Object containing target size and height
45798      * @return {Object} Object containing box measurements for each child, plus meta data
45799      */
45800     calculateChildBoxes: function(visibleItems, targetSize) {
45801         var me = this,
45802             math = Math,
45803             mmax = math.max,
45804             infiniteValue = Infinity,
45805             undefinedValue,
45806
45807             parallelPrefix = me.parallelPrefix,
45808             parallelPrefixCap = me.parallelPrefixCap,
45809             perpendicularPrefix = me.perpendicularPrefix,
45810             perpendicularPrefixCap = me.perpendicularPrefixCap,
45811             parallelMinString = 'min' + parallelPrefixCap,
45812             perpendicularMinString = 'min' + perpendicularPrefixCap,
45813             perpendicularMaxString = 'max' + perpendicularPrefixCap,
45814
45815             parallelSize = targetSize[parallelPrefix] - me.scrollOffset,
45816             perpendicularSize = targetSize[perpendicularPrefix],
45817             padding = me.padding,
45818             parallelOffset = padding[me.parallelBefore],
45819             paddingParallel = parallelOffset + padding[me.parallelAfter],
45820             perpendicularOffset = padding[me.perpendicularLeftTop],
45821             paddingPerpendicular =  perpendicularOffset + padding[me.perpendicularRightBottom],
45822             availPerpendicularSize = mmax(0, perpendicularSize - paddingPerpendicular),
45823
45824             isStart = me.pack == 'start',
45825             isCenter = me.pack == 'center',
45826             isEnd = me.pack == 'end',
45827
45828             constrain = Ext.Number.constrain,
45829             visibleCount = visibleItems.length,
45830             nonFlexSize = 0,
45831             totalFlex = 0,
45832             desiredSize = 0,
45833             minimumSize = 0,
45834             maxSize = 0,
45835             boxes = [],
45836             minSizes = [],
45837             calculatedWidth,
45838
45839             i, child, childParallel, childPerpendicular, childMargins, childSize, minParallel, tmpObj, shortfall, 
45840             tooNarrow, availableSpace, minSize, item, length, itemIndex, box, oldSize, newSize, reduction, diff, 
45841             flexedBoxes, remainingSpace, remainingFlex, flexedSize, parallelMargins, calcs, offset, 
45842             perpendicularMargins, stretchSize;
45843
45844         //gather the total flex of all flexed items and the width taken up by fixed width items
45845         for (i = 0; i < visibleCount; i++) {
45846             child = visibleItems[i];
45847             childPerpendicular = child[perpendicularPrefix];
45848             me.layoutItem(child);
45849             childMargins = child.margins;
45850             parallelMargins = childMargins[me.parallelBefore] + childMargins[me.parallelAfter];
45851
45852             // Create the box description object for this child item.
45853             tmpObj = {
45854                 component: child,
45855                 margins: childMargins
45856             };
45857
45858             // flex and not 'auto' width
45859             if (child.flex) {
45860                 totalFlex += child.flex;
45861                 childParallel = undefinedValue;
45862             }
45863             // Not flexed or 'auto' width or undefined width
45864             else {
45865                 if (!(child[parallelPrefix] && childPerpendicular)) {
45866                     childSize = child.getSize();
45867                 }
45868                 childParallel = child[parallelPrefix] || childSize[parallelPrefix];
45869                 childPerpendicular = childPerpendicular || childSize[perpendicularPrefix];
45870             }
45871
45872             nonFlexSize += parallelMargins + (childParallel || 0);
45873             desiredSize += parallelMargins + (child.flex ? child[parallelMinString] || 0 : childParallel);
45874             minimumSize += parallelMargins + (child[parallelMinString] || childParallel || 0);
45875
45876             // Max height for align - force layout of non-laid out subcontainers without a numeric height
45877             if (typeof childPerpendicular != 'number') {
45878                 // Clear any static sizing and revert to flow so we can get a proper measurement
45879                 // child['set' + perpendicularPrefixCap](null);
45880                 childPerpendicular = child['get' + perpendicularPrefixCap]();
45881             }
45882
45883             // Track the maximum perpendicular size for use by the stretch and stretchmax align config values.
45884             maxSize = mmax(maxSize, childPerpendicular + childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom]);
45885
45886             tmpObj[parallelPrefix] = childParallel || undefinedValue;
45887             tmpObj[perpendicularPrefix] = childPerpendicular || undefinedValue;
45888             boxes.push(tmpObj);
45889         }
45890         shortfall = desiredSize - parallelSize;
45891         tooNarrow = minimumSize > parallelSize;
45892
45893         //the space available to the flexed items
45894         availableSpace = mmax(0, parallelSize - nonFlexSize - paddingParallel - (me.reserveOffset ? me.availableSpaceOffset : 0));
45895
45896         if (tooNarrow) {
45897             for (i = 0; i < visibleCount; i++) {
45898                 box = boxes[i];
45899                 minSize = visibleItems[i][parallelMinString] || visibleItems[i][parallelPrefix] || box[parallelPrefix];
45900                 box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
45901                 box[parallelPrefix] = minSize;
45902             }
45903         }
45904         else {
45905             //all flexed items should be sized to their minimum size, other items should be shrunk down until
45906             //the shortfall has been accounted for
45907             if (shortfall > 0) {
45908                 /*
45909                  * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item.
45910                  * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored.
45911                  * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall.
45912                  */
45913                 for (i = 0; i < visibleCount; i++) {
45914                     item = visibleItems[i];
45915                     minSize = item[parallelMinString] || 0;
45916
45917                     //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
45918                     //shrunk to their minSize because they're flexible and should be the first to lose size
45919                     if (item.flex) {
45920                         box = boxes[i];
45921                         box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
45922                         box[parallelPrefix] = minSize;
45923                     }
45924                     else {
45925                         minSizes.push({
45926                             minSize: minSize,
45927                             available: boxes[i][parallelPrefix] - minSize,
45928                             index: i
45929                         });
45930                     }
45931                 }
45932
45933                 //sort by descending amount of width remaining before minWidth is reached
45934                 Ext.Array.sort(minSizes, me.minSizeSortFn);
45935
45936                 /*
45937                  * Distribute the shortfall (difference between total desired size of all items and actual size available)
45938                  * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
45939                  * smallest difference between their size and minSize first, so that if reducing the size by the average
45940                  * amount would make that item less than its minSize, we carry the remainder over to the next item.
45941                  */
45942                 for (i = 0, length = minSizes.length; i < length; i++) {
45943                     itemIndex = minSizes[i].index;
45944
45945                     if (itemIndex == undefinedValue) {
45946                         continue;
45947                     }
45948                     item = visibleItems[itemIndex];
45949                     minSize = minSizes[i].minSize;
45950
45951                     box = boxes[itemIndex];
45952                     oldSize = box[parallelPrefix];
45953                     newSize = mmax(minSize, oldSize - math.ceil(shortfall / (length - i)));
45954                     reduction = oldSize - newSize;
45955
45956                     box.dirtySize = box.dirtySize || box[parallelPrefix] != newSize;
45957                     box[parallelPrefix] = newSize;
45958                     shortfall -= reduction;
45959                 }
45960             }
45961             else {
45962                 remainingSpace = availableSpace;
45963                 remainingFlex = totalFlex;
45964                 flexedBoxes = [];
45965
45966                 // Create an array containing *just the flexed boxes* for allocation of remainingSpace
45967                 for (i = 0; i < visibleCount; i++) {
45968                     child = visibleItems[i];
45969                     if (isStart && child.flex) {
45970                         flexedBoxes.push(boxes[Ext.Array.indexOf(visibleItems, child)]);
45971                     }
45972                 }
45973                 // The flexed boxes need to be sorted in ascending order of maxSize to work properly
45974                 // so that unallocated space caused by maxWidth being less than flexed width
45975                 // can be reallocated to subsequent flexed boxes.
45976                 Ext.Array.sort(flexedBoxes, me.flexSortFn);
45977
45978                 // Calculate the size of each flexed item, and attempt to set it.
45979                 for (i = 0; i < flexedBoxes.length; i++) {
45980                     calcs = flexedBoxes[i];
45981                     child = calcs.component;
45982                     childMargins = calcs.margins;
45983
45984                     flexedSize = math.ceil((child.flex / remainingFlex) * remainingSpace);
45985
45986                     // Implement maxSize and minSize check
45987                     flexedSize = Math.max(child['min' + parallelPrefixCap] || 0, math.min(child['max' + parallelPrefixCap] || infiniteValue, flexedSize));
45988
45989                     // Remaining space has already had all parallel margins subtracted from it, so just subtract consumed size
45990                     remainingSpace -= flexedSize;
45991                     remainingFlex -= child.flex;
45992
45993                     calcs.dirtySize = calcs.dirtySize || calcs[parallelPrefix] != flexedSize;
45994                     calcs[parallelPrefix] = flexedSize;
45995                 }
45996             }
45997         }
45998
45999         if (isCenter) {
46000             parallelOffset += availableSpace / 2;
46001         }
46002         else if (isEnd) {
46003             parallelOffset += availableSpace;
46004         }
46005
46006         // Fix for left and right docked Components in a dock component layout. This is for docked Headers and docked Toolbars.
46007         // Older Microsoft browsers do not size a position:absolute element's width to match its content.
46008         // So in this case, in the updateInnerCtSize method we may need to adjust the size of the owning Container's element explicitly based upon
46009         // the discovered max width. So here we put a calculatedWidth property in the metadata to facilitate this.
46010         if (me.owner.dock && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) && !me.owner.width && me.direction == 'vertical') {
46011
46012             calculatedWidth = maxSize + me.owner.el.getPadding('lr') + me.owner.el.getBorderWidth('lr');
46013             if (me.owner.frameSize) {
46014                 calculatedWidth += me.owner.frameSize.left + me.owner.frameSize.right;
46015             }
46016             // If the owning element is not sized, calculate the available width to center or stretch in based upon maxSize
46017             availPerpendicularSize = Math.min(availPerpendicularSize, targetSize.width = maxSize + padding.left + padding.right);
46018         }
46019
46020         //finally, calculate the left and top position of each item
46021         for (i = 0; i < visibleCount; i++) {
46022             child = visibleItems[i];
46023             calcs = boxes[i];
46024
46025             childMargins = calcs.margins;
46026
46027             perpendicularMargins = childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom];
46028
46029             // Advance past the "before" margin
46030             parallelOffset += childMargins[me.parallelBefore];
46031
46032             calcs[me.parallelBefore] = parallelOffset;
46033             calcs[me.perpendicularLeftTop] = perpendicularOffset + childMargins[me.perpendicularLeftTop];
46034
46035             if (me.align == 'stretch') {
46036                 stretchSize = constrain(availPerpendicularSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
46037                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
46038                 calcs[perpendicularPrefix] = stretchSize;
46039             }
46040             else if (me.align == 'stretchmax') {
46041                 stretchSize = constrain(maxSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
46042                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
46043                 calcs[perpendicularPrefix] = stretchSize;
46044             }
46045             else if (me.align == me.alignCenteringString) {
46046                 // When calculating a centered position within the content box of the innerCt, the width of the borders must be subtracted from
46047                 // the size to yield the space available to center within.
46048                 // The updateInnerCtSize method explicitly adds the border widths to the set size of the innerCt.
46049                 diff = mmax(availPerpendicularSize, maxSize) - me.innerCt.getBorderWidth(me.perpendicularLT + me.perpendicularRB) - calcs[perpendicularPrefix];
46050                 if (diff > 0) {
46051                     calcs[me.perpendicularLeftTop] = perpendicularOffset + Math.round(diff / 2);
46052                 }
46053             }
46054
46055             // Advance past the box size and the "after" margin
46056             parallelOffset += (calcs[parallelPrefix] || 0) + childMargins[me.parallelAfter];
46057         }
46058
46059         return {
46060             boxes: boxes,
46061             meta : {
46062                 calculatedWidth: calculatedWidth,
46063                 maxSize: maxSize,
46064                 nonFlexSize: nonFlexSize,
46065                 desiredSize: desiredSize,
46066                 minimumSize: minimumSize,
46067                 shortfall: shortfall,
46068                 tooNarrow: tooNarrow
46069             }
46070         };
46071     },
46072     
46073     onRemove: function(comp){
46074         this.callParent(arguments);
46075         if (this.overflowHandler) {
46076             this.overflowHandler.onRemove(comp);
46077         }
46078     },
46079
46080     /**
46081      * @private
46082      */
46083     initOverflowHandler: function() {
46084         var handler = this.overflowHandler;
46085
46086         if (typeof handler == 'string') {
46087             handler = {
46088                 type: handler
46089             };
46090         }
46091
46092         var handlerType = 'None';
46093         if (handler && handler.type !== undefined) {
46094             handlerType = handler.type;
46095         }
46096
46097         var constructor = Ext.layout.container.boxOverflow[handlerType];
46098         if (constructor[this.type]) {
46099             constructor = constructor[this.type];
46100         }
46101
46102         this.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.' + handlerType, this, handler);
46103     },
46104
46105     /**
46106      * @private
46107      * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
46108      * when laying out
46109      */
46110     onLayout: function() {
46111         this.callParent();
46112         // Clear the innerCt size so it doesn't influence the child items.
46113         if (this.clearInnerCtOnLayout === true && this.adjustmentPass !== true) {
46114             this.innerCt.setSize(null, null);
46115         }
46116
46117         var me = this,
46118             targetSize = me.getLayoutTargetSize(),
46119             items = me.getVisibleItems(),
46120             calcs = me.calculateChildBoxes(items, targetSize),
46121             boxes = calcs.boxes,
46122             meta = calcs.meta,
46123             handler, method, results;
46124
46125         if (me.autoSize && calcs.meta.desiredSize) {
46126             targetSize[me.parallelPrefix] = calcs.meta.desiredSize;
46127         }
46128
46129         //invoke the overflow handler, if one is configured
46130         if (meta.shortfall > 0) {
46131             handler = me.overflowHandler;
46132             method = meta.tooNarrow ? 'handleOverflow': 'clearOverflow';
46133
46134             results = handler[method](calcs, targetSize);
46135
46136             if (results) {
46137                 if (results.targetSize) {
46138                     targetSize = results.targetSize;
46139                 }
46140
46141                 if (results.recalculate) {
46142                     items = me.getVisibleItems(owner);
46143                     calcs = me.calculateChildBoxes(items, targetSize);
46144                     boxes = calcs.boxes;
46145                 }
46146             }
46147         } else {
46148             me.overflowHandler.clearOverflow();
46149         }
46150
46151         /**
46152          * @private
46153          * @property layoutTargetLastSize
46154          * @type Object
46155          * Private cache of the last measured size of the layout target. This should never be used except by
46156          * BoxLayout subclasses during their onLayout run.
46157          */
46158         me.layoutTargetLastSize = targetSize;
46159
46160         /**
46161          * @private
46162          * @property childBoxCache
46163          * @type Array
46164          * Array of the last calculated height, width, top and left positions of each visible rendered component
46165          * within the Box layout.
46166          */
46167         me.childBoxCache = calcs;
46168
46169         me.updateInnerCtSize(targetSize, calcs);
46170         me.updateChildBoxes(boxes);
46171         me.handleTargetOverflow(targetSize);
46172     },
46173
46174     /**
46175      * Resizes and repositions each child component
46176      * @param {Array} boxes The box measurements
46177      */
46178     updateChildBoxes: function(boxes) {
46179         var me = this,
46180             i = 0,
46181             length = boxes.length,
46182             animQueue = [],
46183             dd = Ext.dd.DDM.getDDById(me.innerCt.id), // Any DD active on this layout's element (The BoxReorderer plugin does this.)
46184             oldBox, newBox, changed, comp, boxAnim, animCallback;
46185
46186         for (; i < length; i++) {
46187             newBox = boxes[i];
46188             comp = newBox.component;
46189
46190             // If a Component is being drag/dropped, skip positioning it.
46191             // Accomodate the BoxReorderer plugin: Its current dragEl must not be positioned by the layout
46192             if (dd && (dd.getDragEl() === comp.el.dom)) {
46193                 continue;
46194             }
46195
46196             changed = false;
46197
46198             oldBox = me.getChildBox(comp);
46199
46200             // If we are animating, we build up an array of Anim config objects, one for each
46201             // child Component which has any changed box properties. Those with unchanged
46202             // properties are not animated.
46203             if (me.animate) {
46204                 // Animate may be a config object containing callback.
46205                 animCallback = me.animate.callback || me.animate;
46206                 boxAnim = {
46207                     layoutAnimation: true,  // Component Target handler must use set*Calculated*Size
46208                     target: comp,
46209                     from: {},
46210                     to: {},
46211                     listeners: {}
46212                 };
46213                 // Only set from and to properties when there's a change.
46214                 // Perform as few Component setter methods as possible.
46215                 // Temporarily set the property values that we are not animating
46216                 // so that doComponentLayout does not auto-size them.
46217                 if (!isNaN(newBox.width) && (newBox.width != oldBox.width)) {
46218                     changed = true;
46219                     // boxAnim.from.width = oldBox.width;
46220                     boxAnim.to.width = newBox.width;
46221                 }
46222                 if (!isNaN(newBox.height) && (newBox.height != oldBox.height)) {
46223                     changed = true;
46224                     // boxAnim.from.height = oldBox.height;
46225                     boxAnim.to.height = newBox.height;
46226                 }
46227                 if (!isNaN(newBox.left) && (newBox.left != oldBox.left)) {
46228                     changed = true;
46229                     // boxAnim.from.left = oldBox.left;
46230                     boxAnim.to.left = newBox.left;
46231                 }
46232                 if (!isNaN(newBox.top) && (newBox.top != oldBox.top)) {
46233                     changed = true;
46234                     // boxAnim.from.top = oldBox.top;
46235                     boxAnim.to.top = newBox.top;
46236                 }
46237                 if (changed) {
46238                     animQueue.push(boxAnim);
46239                 }
46240             } else {
46241                 if (newBox.dirtySize) {
46242                     if (newBox.width !== oldBox.width || newBox.height !== oldBox.height) {
46243                         me.setItemSize(comp, newBox.width, newBox.height);
46244                     }
46245                 }
46246                 // Don't set positions to NaN
46247                 if (isNaN(newBox.left) || isNaN(newBox.top)) {
46248                     continue;
46249                 }
46250                 comp.setPosition(newBox.left, newBox.top);
46251             }
46252         }
46253
46254         // Kick off any queued animations
46255         length = animQueue.length;
46256         if (length) {
46257
46258             // A function which cleans up when a Component's animation is done.
46259             // The last one to finish calls the callback.
46260             var afterAnimate = function(anim) {
46261                 // When we've animated all changed boxes into position, clear our busy flag and call the callback.
46262                 length -= 1;
46263                 if (!length) {
46264                     me.layoutBusy = false;
46265                     if (Ext.isFunction(animCallback)) {
46266                         animCallback();
46267                     }
46268                 }
46269             };
46270
46271             var beforeAnimate = function() {
46272                 me.layoutBusy = true;
46273             };
46274
46275             // Start each box animation off
46276             for (i = 0, length = animQueue.length; i < length; i++) {
46277                 boxAnim = animQueue[i];
46278
46279                 // Clean up the Component after. Clean up the *layout* after the last animation finishes
46280                 boxAnim.listeners.afteranimate = afterAnimate;
46281
46282                 // The layout is busy during animation, and may not be called, so set the flag when the first animation begins
46283                 if (!i) {
46284                     boxAnim.listeners.beforeanimate = beforeAnimate;
46285                 }
46286                 if (me.animate.duration) {
46287                     boxAnim.duration = me.animate.duration;
46288                 }
46289                 comp = boxAnim.target;
46290                 delete boxAnim.target;
46291                 // Stop any currently running animation
46292                 comp.stopAnimation();
46293                 comp.animate(boxAnim);
46294             }
46295         }
46296     },
46297
46298     /**
46299      * @private
46300      * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
46301      * to make sure all child items fit within it. We call this before sizing the children because if our child
46302      * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
46303      * again immediately afterwards, giving a performance hit.
46304      * Subclasses should provide an implementation.
46305      * @param {Object} currentSize The current height and width of the innerCt
46306      * @param {Array} calculations The new box calculations of all items to be laid out
46307      */
46308     updateInnerCtSize: function(tSize, calcs) {
46309         var me = this,
46310             mmax = Math.max,
46311             align = me.align,
46312             padding = me.padding,
46313             width = tSize.width,
46314             height = tSize.height,
46315             meta = calcs.meta,
46316             innerCtWidth,
46317             innerCtHeight;
46318
46319         if (me.direction == 'horizontal') {
46320             innerCtWidth = width;
46321             innerCtHeight = meta.maxSize + padding.top + padding.bottom + me.innerCt.getBorderWidth('tb');
46322
46323             if (align == 'stretch') {
46324                 innerCtHeight = height;
46325             }
46326             else if (align == 'middle') {
46327                 innerCtHeight = mmax(height, innerCtHeight);
46328             }
46329         } else {
46330             innerCtHeight = height;
46331             innerCtWidth = meta.maxSize + padding.left + padding.right + me.innerCt.getBorderWidth('lr');
46332
46333             if (align == 'stretch') {
46334                 innerCtWidth = width;
46335             }
46336             else if (align == 'center') {
46337                 innerCtWidth = mmax(width, innerCtWidth);
46338             }
46339         }
46340         me.getRenderTarget().setSize(innerCtWidth || undefined, innerCtHeight || undefined);
46341
46342         // If a calculated width has been found (and this only happens for auto-width vertical docked Components in old Microsoft browsers)
46343         // then, if the Component has not assumed the size of its content, set it to do so.
46344         if (meta.calculatedWidth && me.owner.el.getWidth() > meta.calculatedWidth) {
46345             me.owner.el.setWidth(meta.calculatedWidth);
46346         }
46347
46348         if (me.innerCt.dom.scrollTop) {
46349             me.innerCt.dom.scrollTop = 0;
46350         }
46351     },
46352
46353     /**
46354      * @private
46355      * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
46356      * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
46357      * target. Having a Box layout inside such a target is therefore not recommended.
46358      * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
46359      * @param {Ext.container.Container} container The container
46360      * @param {Ext.core.Element} target The target element
46361      * @return True if the layout overflowed, and was reflowed in a secondary onLayout call.
46362      */
46363     handleTargetOverflow: function(previousTargetSize) {
46364         var target = this.getTarget(),
46365             overflow = target.getStyle('overflow'),
46366             newTargetSize;
46367
46368         if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
46369             newTargetSize = this.getLayoutTargetSize();
46370             if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height) {
46371                 this.adjustmentPass = true;
46372                 this.onLayout();
46373                 return true;
46374             }
46375         }
46376
46377         delete this.adjustmentPass;
46378     },
46379
46380     // private
46381     isValidParent : function(item, target, position) {
46382         // Note: Box layouts do not care about order within the innerCt element because it's an absolutely positioning layout
46383         // We only care whether the item is a direct child of the innerCt element.
46384         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
46385         return (itemEl && this.innerCt && itemEl.parentNode === this.innerCt.dom) || false;
46386     },
46387
46388     // Overridden method from AbstractContainer.
46389     // Used in the base AbstractLayout.beforeLayout method to render all items into.
46390     getRenderTarget: function() {
46391         if (!this.innerCt) {
46392             // the innerCt prevents wrapping and shuffling while the container is resizing
46393             this.innerCt = this.getTarget().createChild({
46394                 cls: this.innerCls,
46395                 role: 'presentation'
46396             });
46397             this.padding = Ext.util.Format.parseBox(this.padding);
46398         }
46399         return this.innerCt;
46400     },
46401
46402     // private
46403     renderItem: function(item, target) {
46404         this.callParent(arguments);
46405         var me = this,
46406             itemEl = item.getEl(),
46407             style = itemEl.dom.style,
46408             margins = item.margins || item.margin;
46409
46410         // Parse the item's margin/margins specification
46411         if (margins) {
46412             if (Ext.isString(margins) || Ext.isNumber(margins)) {
46413                 margins = Ext.util.Format.parseBox(margins);
46414             } else {
46415                 Ext.applyIf(margins, {top: 0, right: 0, bottom: 0, left: 0});
46416             }
46417         } else {
46418             margins = Ext.apply({}, me.defaultMargins);
46419         }
46420
46421         // Add any before/after CSS margins to the configured margins, and zero the CSS margins
46422         margins.top    += itemEl.getMargin('t');
46423         margins.right  += itemEl.getMargin('r');
46424         margins.bottom += itemEl.getMargin('b');
46425         margins.left   += itemEl.getMargin('l');
46426         style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = '0';
46427
46428         // Item must reference calculated margins.
46429         item.margins = margins;
46430     },
46431
46432     /**
46433      * @private
46434      */
46435     destroy: function() {
46436         Ext.destroy(this.overflowHandler);
46437         this.callParent(arguments);
46438     }
46439 });
46440 /**
46441  * @class Ext.layout.container.HBox
46442  * @extends Ext.layout.container.Box
46443  * <p>A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
46444  * space between child items containing a numeric <code>flex</code> configuration.</p>
46445  * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
46446  * {@img Ext.layout.container.HBox/Ext.layout.container.HBox.png Ext.layout.container.HBox container layout}
46447  * Example usage:
46448     Ext.create('Ext.Panel', {
46449         width: 500,
46450         height: 300,
46451         title: "HBoxLayout Panel",
46452         layout: {
46453             type: 'hbox',
46454             align: 'stretch'
46455         },
46456         renderTo: document.body,
46457         items: [{
46458             xtype: 'panel',
46459             title: 'Inner Panel One',
46460             flex: 2
46461         },{
46462             xtype: 'panel',
46463             title: 'Inner Panel Two',
46464             flex: 1
46465         },{
46466             xtype: 'panel',
46467             title: 'Inner Panel Three',
46468             flex: 1
46469         }]
46470     });
46471  */
46472 Ext.define('Ext.layout.container.HBox', {
46473
46474     /* Begin Definitions */
46475
46476     alias: ['layout.hbox'],
46477     extend: 'Ext.layout.container.Box',
46478     alternateClassName: 'Ext.layout.HBoxLayout',
46479     
46480     /* End Definitions */
46481
46482     /**
46483      * @cfg {String} align
46484      * Controls how the child items of the container are aligned. Acceptable configuration values for this
46485      * property are:
46486      * <div class="mdetail-params"><ul>
46487      * <li><b><tt>top</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned vertically
46488      * at the <b>top</b> of the container</div></li>
46489      * <li><b><tt>middle</tt></b> : <div class="sub-desc">child items are aligned vertically in the
46490      * <b>middle</b> of the container</div></li>
46491      * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched vertically to fill
46492      * the height of the container</div></li>
46493      * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched vertically to
46494      * the height of the largest item.</div></li>
46495      * </ul></div>
46496      */
46497     align: 'top', // top, middle, stretch, strechmax
46498
46499     //@private
46500     alignCenteringString: 'middle',
46501
46502     type : 'hbox',
46503
46504     direction: 'horizontal',
46505
46506     // When creating an argument list to setSize, use this order
46507     parallelSizeIndex: 0,
46508     perpendicularSizeIndex: 1,
46509
46510     parallelPrefix: 'width',
46511     parallelPrefixCap: 'Width',
46512     parallelLT: 'l',
46513     parallelRB: 'r',
46514     parallelBefore: 'left',
46515     parallelBeforeCap: 'Left',
46516     parallelAfter: 'right',
46517     parallelPosition: 'x',
46518
46519     perpendicularPrefix: 'height',
46520     perpendicularPrefixCap: 'Height',
46521     perpendicularLT: 't',
46522     perpendicularRB: 'b',
46523     perpendicularLeftTop: 'top',
46524     perpendicularRightBottom: 'bottom',
46525     perpendicularPosition: 'y'
46526 });
46527 /**
46528  * @class Ext.layout.container.VBox
46529  * @extends Ext.layout.container.Box
46530  * <p>A layout that arranges items vertically down a Container. This layout optionally divides available vertical
46531  * space between child items containing a numeric <code>flex</code> configuration.</p>
46532  * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
46533  * {@img Ext.layout.container.VBox/Ext.layout.container.VBox.png Ext.layout.container.VBox container layout}
46534  * Example usage:
46535         Ext.create('Ext.Panel', {
46536                 width: 500,
46537                 height: 400,
46538                 title: "VBoxLayout Panel",
46539                 layout: {                        
46540                         type: 'vbox',
46541                         align: 'center'
46542                 },
46543                 renderTo: document.body,
46544                 items: [{                        
46545                         xtype: 'panel',
46546                         title: 'Inner Panel One',
46547                         width: 250,
46548                         flex: 2                      
46549                 },{
46550                         xtype: 'panel',
46551                         title: 'Inner Panel Two',
46552                         width: 250,                     
46553                         flex: 4
46554                 },{
46555                         xtype: 'panel',
46556                         title: 'Inner Panel Three',
46557                         width: '50%',                   
46558                         flex: 4
46559                 }]
46560         });
46561  */
46562 Ext.define('Ext.layout.container.VBox', {
46563
46564     /* Begin Definitions */
46565
46566     alias: ['layout.vbox'],
46567     extend: 'Ext.layout.container.Box',
46568     alternateClassName: 'Ext.layout.VBoxLayout',
46569     
46570     /* End Definitions */
46571
46572     /**
46573      * @cfg {String} align
46574      * Controls how the child items of the container are aligned. Acceptable configuration values for this
46575      * property are:
46576      * <div class="mdetail-params"><ul>
46577      * <li><b><tt>left</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned horizontally
46578      * at the <b>left</b> side of the container</div></li>
46579      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are aligned horizontally at the
46580      * <b>mid-width</b> of the container</div></li>
46581      * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched horizontally to fill
46582      * the width of the container</div></li>
46583      * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched horizontally to
46584      * the size of the largest item.</div></li>
46585      * </ul></div>
46586      */
46587     align : 'left', // left, center, stretch, strechmax
46588
46589     //@private
46590     alignCenteringString: 'center',
46591
46592     type: 'vbox',
46593
46594     direction: 'vertical',
46595
46596     // When creating an argument list to setSize, use this order
46597     parallelSizeIndex: 1,
46598     perpendicularSizeIndex: 0,
46599
46600     parallelPrefix: 'height',
46601     parallelPrefixCap: 'Height',
46602     parallelLT: 't',
46603     parallelRB: 'b',
46604     parallelBefore: 'top',
46605     parallelBeforeCap: 'Top',
46606     parallelAfter: 'bottom',
46607     parallelPosition: 'y',
46608
46609     perpendicularPrefix: 'width',
46610     perpendicularPrefixCap: 'Width',
46611     perpendicularLT: 'l',
46612     perpendicularRB: 'r',
46613     perpendicularLeftTop: 'left',
46614     perpendicularRightBottom: 'right',
46615     perpendicularPosition: 'x'
46616 });
46617 /**
46618  * @class Ext.FocusManager
46619
46620 The FocusManager is responsible for globally:
46621
46622 1. Managing component focus
46623 2. Providing basic keyboard navigation
46624 3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
46625
46626 To activate the FocusManager, simply call {@link #enable `Ext.FocusManager.enable();`}. In turn, you may
46627 deactivate the FocusManager by subsequently calling {@link #disable `Ext.FocusManager.disable();`}.  The
46628 FocusManager is disabled by default.
46629
46630 To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
46631
46632 Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
46633 that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
46634 call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
46635 {@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
46636
46637  * @singleton
46638  * @markdown
46639  * @author Jarred Nicholls <jarred@sencha.com>
46640  * @docauthor Jarred Nicholls <jarred@sencha.com>
46641  */
46642 Ext.define('Ext.FocusManager', {
46643     singleton: true,
46644     alternateClassName: 'Ext.FocusMgr',
46645
46646     mixins: {
46647         observable: 'Ext.util.Observable'
46648     },
46649
46650     requires: [
46651         'Ext.ComponentManager',
46652         'Ext.ComponentQuery',
46653         'Ext.util.HashMap',
46654         'Ext.util.KeyNav'
46655     ],
46656
46657     /**
46658      * @property {Boolean} enabled
46659      * Whether or not the FocusManager is currently enabled
46660      */
46661     enabled: false,
46662
46663     /**
46664      * @property {Ext.Component} focusedCmp
46665      * The currently focused component. Defaults to `undefined`.
46666      * @markdown
46667      */
46668
46669     focusElementCls: Ext.baseCSSPrefix + 'focus-element',
46670
46671     focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
46672
46673     /**
46674      * @property {Array} whitelist
46675      * A list of xtypes that should ignore certain navigation input keys and
46676      * allow for the default browser event/behavior. These input keys include:
46677      *
46678      * 1. Backspace
46679      * 2. Delete
46680      * 3. Left
46681      * 4. Right
46682      * 5. Up
46683      * 6. Down
46684      *
46685      * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
46686      * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
46687      * the user to move the input cursor left and right, and to delete characters, etc.
46688      *
46689      * This whitelist currently defaults to `['textfield']`.
46690      * @markdown
46691      */
46692     whitelist: [
46693         'textfield'
46694     ],
46695
46696     tabIndexWhitelist: [
46697         'a',
46698         'button',
46699         'embed',
46700         'frame',
46701         'iframe',
46702         'img',
46703         'input',
46704         'object',
46705         'select',
46706         'textarea'
46707     ],
46708
46709     constructor: function() {
46710         var me = this,
46711             CQ = Ext.ComponentQuery;
46712
46713         me.addEvents(
46714             /**
46715              * @event beforecomponentfocus
46716              * Fires before a component becomes focused. Return `false` to prevent
46717              * the component from gaining focus.
46718              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
46719              * @param {Ext.Component} cmp The component that is being focused
46720              * @param {Ext.Component} previousCmp The component that was previously focused,
46721              * or `undefined` if there was no previously focused component.
46722              * @markdown
46723              */
46724             'beforecomponentfocus',
46725
46726             /**
46727              * @event componentfocus
46728              * Fires after a component becomes focused.
46729              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
46730              * @param {Ext.Component} cmp The component that has been focused
46731              * @param {Ext.Component} previousCmp The component that was previously focused,
46732              * or `undefined` if there was no previously focused component.
46733              * @markdown
46734              */
46735             'componentfocus',
46736
46737             /**
46738              * @event disable
46739              * Fires when the FocusManager is disabled
46740              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
46741              */
46742             'disable',
46743
46744             /**
46745              * @event enable
46746              * Fires when the FocusManager is enabled
46747              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
46748              */
46749             'enable'
46750         );
46751
46752         // Setup KeyNav that's bound to document to catch all
46753         // unhandled/bubbled key events for navigation
46754         me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
46755             disabled: true,
46756             scope: me,
46757
46758             backspace: me.focusLast,
46759             enter: me.navigateIn,
46760             esc: me.navigateOut,
46761             tab: me.navigateSiblings
46762
46763             //space: me.navigateIn,
46764             //del: me.focusLast,
46765             //left: me.navigateSiblings,
46766             //right: me.navigateSiblings,
46767             //down: me.navigateSiblings,
46768             //up: me.navigateSiblings
46769         });
46770
46771         me.focusData = {};
46772         me.subscribers = Ext.create('Ext.util.HashMap');
46773         me.focusChain = {};
46774
46775         // Setup some ComponentQuery pseudos
46776         Ext.apply(CQ.pseudos, {
46777             focusable: function(cmps) {
46778                 var len = cmps.length,
46779                     results = [],
46780                     i = 0,
46781                     c,
46782
46783                     isFocusable = function(x) {
46784                         return x && x.focusable !== false && CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el && c.el.dom && c.el.isVisible()}');
46785                     };
46786
46787                 for (; i < len; i++) {
46788                     c = cmps[i];
46789                     if (isFocusable(c)) {
46790                         results.push(c);
46791                     }
46792                 }
46793
46794                 return results;
46795             },
46796
46797             nextFocus: function(cmps, idx, step) {
46798                 step = step || 1;
46799                 idx = parseInt(idx, 10);
46800
46801                 var len = cmps.length,
46802                     i = idx + step,
46803                     c;
46804
46805                 for (; i != idx; i += step) {
46806                     if (i >= len) {
46807                         i = 0;
46808                     } else if (i < 0) {
46809                         i = len - 1;
46810                     }
46811
46812                     c = cmps[i];
46813                     if (CQ.is(c, ':focusable')) {
46814                         return [c];
46815                     } else if (c.placeholder && CQ.is(c.placeholder, ':focusable')) {
46816                         return [c.placeholder];
46817                     }
46818                 }
46819
46820                 return [];
46821             },
46822
46823             prevFocus: function(cmps, idx) {
46824                 return this.nextFocus(cmps, idx, -1);
46825             },
46826
46827             root: function(cmps) {
46828                 var len = cmps.length,
46829                     results = [],
46830                     i = 0,
46831                     c;
46832
46833                 for (; i < len; i++) {
46834                     c = cmps[i];
46835                     if (!c.ownerCt) {
46836                         results.push(c);
46837                     }
46838                 }
46839
46840                 return results;
46841             }
46842         });
46843     },
46844
46845     /**
46846      * Adds the specified xtype to the {@link #whitelist}.
46847      * @param {String/Array} xtype Adds the xtype(s) to the {@link #whitelist}.
46848      */
46849     addXTypeToWhitelist: function(xtype) {
46850         var me = this;
46851
46852         if (Ext.isArray(xtype)) {
46853             Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
46854             return;
46855         }
46856
46857         if (!Ext.Array.contains(me.whitelist, xtype)) {
46858             me.whitelist.push(xtype);
46859         }
46860     },
46861
46862     clearComponent: function(cmp) {
46863         clearTimeout(this.cmpFocusDelay);
46864         if (!cmp.isDestroyed) {
46865             cmp.blur();
46866         }
46867     },
46868
46869     /**
46870      * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
46871      */
46872     disable: function() {
46873         var me = this;
46874
46875         if (!me.enabled) {
46876             return;
46877         }
46878
46879         delete me.options;
46880         me.enabled = false;
46881
46882         Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
46883
46884         me.removeDOM();
46885
46886         // Stop handling key navigation
46887         me.keyNav.disable();
46888
46889         // disable focus for all components
46890         me.setFocusAll(false);
46891
46892         me.fireEvent('disable', me);
46893     },
46894
46895     /**
46896      * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
46897      * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
46898         - focusFrame : Boolean
46899             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
46900      * @markdown
46901      */
46902     enable: function(options) {
46903         var me = this;
46904
46905         if (options === true) {
46906             options = { focusFrame: true };
46907         }
46908         me.options = options = options || {};
46909
46910         if (me.enabled) {
46911             return;
46912         }
46913
46914         // Handle components that are newly added after we are enabled
46915         Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
46916
46917         me.initDOM(options);
46918
46919         // Start handling key navigation
46920         me.keyNav.enable();
46921
46922         // enable focus for all components
46923         me.setFocusAll(true, options);
46924
46925         // Finally, let's focus our global focus el so we start fresh
46926         me.focusEl.focus();
46927         delete me.focusedCmp;
46928
46929         me.enabled = true;
46930         me.fireEvent('enable', me);
46931     },
46932
46933     focusLast: function(e) {
46934         var me = this;
46935
46936         if (me.isWhitelisted(me.focusedCmp)) {
46937             return true;
46938         }
46939
46940         // Go back to last focused item
46941         if (me.previousFocusedCmp) {
46942             me.previousFocusedCmp.focus();
46943         }
46944     },
46945
46946     getRootComponents: function() {
46947         var me = this,
46948             CQ = Ext.ComponentQuery,
46949             inline = CQ.query(':focusable:root:not([floating])'),
46950             floating = CQ.query(':focusable:root[floating]');
46951
46952         // Floating items should go to the top of our root stack, and be ordered
46953         // by their z-index (highest first)
46954         floating.sort(function(a, b) {
46955             return a.el.getZIndex() > b.el.getZIndex();
46956         });
46957
46958         return floating.concat(inline);
46959     },
46960
46961     initDOM: function(options) {
46962         var me = this,
46963             sp = '&#160',
46964             cls = me.focusFrameCls;
46965
46966         if (!Ext.isReady) {
46967             Ext.onReady(me.initDOM, me);
46968             return;
46969         }
46970
46971         // Create global focus element
46972         if (!me.focusEl) {
46973             me.focusEl = Ext.getBody().createChild({
46974                 tabIndex: '-1',
46975                 cls: me.focusElementCls,
46976                 html: sp
46977             });
46978         }
46979
46980         // Create global focus frame
46981         if (!me.focusFrame && options.focusFrame) {
46982             me.focusFrame = Ext.getBody().createChild({
46983                 cls: cls,
46984                 children: [
46985                     { cls: cls + '-top' },
46986                     { cls: cls + '-bottom' },
46987                     { cls: cls + '-left' },
46988                     { cls: cls + '-right' }
46989                 ],
46990                 style: 'top: -100px; left: -100px;'
46991             });
46992             me.focusFrame.setVisibilityMode(Ext.core.Element.DISPLAY);
46993             me.focusFrameWidth = me.focusFrame.child('.' + cls + '-top').getHeight();
46994             me.focusFrame.hide().setLeftTop(0, 0);
46995         }
46996     },
46997
46998     isWhitelisted: function(cmp) {
46999         return cmp && Ext.Array.some(this.whitelist, function(x) {
47000             return cmp.isXType(x);
47001         });
47002     },
47003
47004     navigateIn: function(e) {
47005         var me = this,
47006             focusedCmp = me.focusedCmp,
47007             rootCmps,
47008             firstChild;
47009
47010         if (!focusedCmp) {
47011             // No focus yet, so focus the first root cmp on the page
47012             rootCmps = me.getRootComponents();
47013             if (rootCmps.length) {
47014                 rootCmps[0].focus();
47015             }
47016         } else {
47017             // Drill into child ref items of the focused cmp, if applicable.
47018             // This works for any Component with a getRefItems implementation.
47019             firstChild = Ext.ComponentQuery.query('>:focusable', focusedCmp)[0];
47020             if (firstChild) {
47021                 firstChild.focus();
47022             } else {
47023                 // Let's try to fire a click event, as if it came from the mouse
47024                 if (Ext.isFunction(focusedCmp.onClick)) {
47025                     e.button = 0;
47026                     focusedCmp.onClick(e);
47027                     focusedCmp.focus();
47028                 }
47029             }
47030         }
47031     },
47032
47033     navigateOut: function(e) {
47034         var me = this,
47035             parent;
47036
47037         if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
47038             me.focusEl.focus();
47039             return;
47040         }
47041
47042         parent.focus();
47043     },
47044
47045     navigateSiblings: function(e, source, parent) {
47046         var me = this,
47047             src = source || me,
47048             key = e.getKey(),
47049             EO = Ext.EventObject,
47050             goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
47051             checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
47052             nextSelector = goBack ? 'prev' : 'next',
47053             idx, next, focusedCmp;
47054
47055         focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
47056         if (!focusedCmp && !parent) {
47057             return;
47058         }
47059
47060         if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
47061             return true;
47062         }
47063
47064         parent = parent || focusedCmp.up();
47065         if (parent) {
47066             idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
47067             next = Ext.ComponentQuery.query('>:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
47068             if (next && focusedCmp !== next) {
47069                 next.focus();
47070                 return next;
47071             }
47072         }
47073     },
47074
47075     onComponentBlur: function(cmp, e) {
47076         var me = this;
47077
47078         if (me.focusedCmp === cmp) {
47079             me.previousFocusedCmp = cmp;
47080             delete me.focusedCmp;
47081         }
47082
47083         if (me.focusFrame) {
47084             me.focusFrame.hide();
47085         }
47086     },
47087
47088     onComponentCreated: function(hash, id, cmp) {
47089         this.setFocus(cmp, true, this.options);
47090     },
47091
47092     onComponentDestroy: function(cmp) {
47093         this.setFocus(cmp, false);
47094     },
47095
47096     onComponentFocus: function(cmp, e) {
47097         var me = this,
47098             chain = me.focusChain;
47099
47100         if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
47101             me.clearComponent(cmp);
47102
47103             // Check our focus chain, so we don't run into a never ending recursion
47104             // If we've attempted (unsuccessfully) to focus this component before,
47105             // then we're caught in a loop of child->parent->...->child and we
47106             // need to cut the loop off rather than feed into it.
47107             if (chain[cmp.id]) {
47108                 return;
47109             }
47110
47111             // Try to focus the parent instead
47112             var parent = cmp.up();
47113             if (parent) {
47114                 // Add component to our focus chain to detect infinite focus loop
47115                 // before we fire off an attempt to focus our parent.
47116                 // See the comments above.
47117                 chain[cmp.id] = true;
47118                 parent.focus();
47119             }
47120
47121             return;
47122         }
47123
47124         // Clear our focus chain when we have a focusable component
47125         me.focusChain = {};
47126
47127         // Defer focusing for 90ms so components can do a layout/positioning
47128         // and give us an ability to buffer focuses
47129         clearTimeout(me.cmpFocusDelay);
47130         if (arguments.length !== 2) {
47131             me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
47132             return;
47133         }
47134
47135         if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
47136             me.clearComponent(cmp);
47137             return;
47138         }
47139
47140         me.focusedCmp = cmp;
47141
47142         // If we have a focus frame, show it around the focused component
47143         if (me.shouldShowFocusFrame(cmp)) {
47144             var cls = '.' + me.focusFrameCls + '-',
47145                 ff = me.focusFrame,
47146                 fw = me.focusFrameWidth,
47147                 box = cmp.el.getPageBox(),
47148
47149             // Size the focus frame's t/b/l/r according to the box
47150             // This leaves a hole in the middle of the frame so user
47151             // interaction w/ the mouse can continue
47152                 bt = box.top,
47153                 bl = box.left,
47154                 bw = box.width,
47155                 bh = box.height,
47156                 ft = ff.child(cls + 'top'),
47157                 fb = ff.child(cls + 'bottom'),
47158                 fl = ff.child(cls + 'left'),
47159                 fr = ff.child(cls + 'right');
47160
47161             ft.setWidth(bw - 2).setLeftTop(bl + 1, bt);
47162             fb.setWidth(bw - 2).setLeftTop(bl + 1, bt + bh - fw);
47163             fl.setHeight(bh - 2).setLeftTop(bl, bt + 1);
47164             fr.setHeight(bh - 2).setLeftTop(bl + bw - fw, bt + 1);
47165
47166             ff.show();
47167         }
47168
47169         me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
47170     },
47171
47172     onComponentHide: function(cmp) {
47173         var me = this,
47174             CQ = Ext.ComponentQuery,
47175             cmpHadFocus = false,
47176             focusedCmp,
47177             parent;
47178
47179         if (me.focusedCmp) {
47180             focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
47181             cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
47182
47183             if (focusedCmp) {
47184                 me.clearComponent(focusedCmp);
47185             }
47186         }
47187
47188         me.clearComponent(cmp);
47189
47190         if (cmpHadFocus) {
47191             parent = CQ.query('^:focusable', cmp)[0];
47192             if (parent) {
47193                 parent.focus();
47194             }
47195         }
47196     },
47197
47198     removeDOM: function() {
47199         var me = this;
47200
47201         // If we are still enabled globally, or there are still subscribers
47202         // then we will halt here, since our DOM stuff is still being used
47203         if (me.enabled || me.subscribers.length) {
47204             return;
47205         }
47206
47207         Ext.destroy(
47208             me.focusEl,
47209             me.focusFrame
47210         );
47211         delete me.focusEl;
47212         delete me.focusFrame;
47213         delete me.focusFrameWidth;
47214     },
47215
47216     /**
47217      * Removes the specified xtype from the {@link #whitelist}.
47218      * @param {String/Array} xtype Removes the xtype(s) from the {@link #whitelist}.
47219      */
47220     removeXTypeFromWhitelist: function(xtype) {
47221         var me = this;
47222
47223         if (Ext.isArray(xtype)) {
47224             Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
47225             return;
47226         }
47227
47228         Ext.Array.remove(me.whitelist, xtype);
47229     },
47230
47231     setFocus: function(cmp, focusable, options) {
47232         var me = this,
47233             el, dom, data,
47234
47235             needsTabIndex = function(n) {
47236                 return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
47237                     && n.tabIndex <= 0;
47238             };
47239
47240         options = options || {};
47241
47242         // Come back and do this after the component is rendered
47243         if (!cmp.rendered) {
47244             cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
47245             return;
47246         }
47247
47248         el = cmp.getFocusEl();
47249         dom = el.dom;
47250
47251         // Decorate the component's focus el for focus-ability
47252         if ((focusable && !me.focusData[cmp.id]) || (!focusable && me.focusData[cmp.id])) {
47253             if (focusable) {
47254                 data = {
47255                     focusFrame: options.focusFrame
47256                 };
47257
47258                 // Only set -1 tabIndex if we need it
47259                 // inputs, buttons, and anchor tags do not need it,
47260                 // and neither does any DOM that has it set already
47261                 // programmatically or in markup.
47262                 if (needsTabIndex(dom)) {
47263                     data.tabIndex = dom.tabIndex;
47264                     dom.tabIndex = -1;
47265                 }
47266
47267                 el.on({
47268                     focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
47269                     blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
47270                     scope: me
47271                 });
47272                 cmp.on({
47273                     hide: me.onComponentHide,
47274                     close: me.onComponentHide,
47275                     beforedestroy: me.onComponentDestroy,
47276                     scope: me
47277                 });
47278
47279                 me.focusData[cmp.id] = data;
47280             } else {
47281                 data = me.focusData[cmp.id];
47282                 if ('tabIndex' in data) {
47283                     dom.tabIndex = data.tabIndex;
47284                 }
47285                 el.un('focus', data.focusFn, me);
47286                 el.un('blur', data.blurFn, me);
47287                 cmp.un('hide', me.onComponentHide, me);
47288                 cmp.un('close', me.onComponentHide, me);
47289                 cmp.un('beforedestroy', me.onComponentDestroy, me);
47290
47291                 delete me.focusData[cmp.id];
47292             }
47293         }
47294     },
47295
47296     setFocusAll: function(focusable, options) {
47297         var me = this,
47298             cmps = Ext.ComponentManager.all.getArray(),
47299             len = cmps.length,
47300             cmp,
47301             i = 0;
47302
47303         for (; i < len; i++) {
47304             me.setFocus(cmps[i], focusable, options);
47305         }
47306     },
47307
47308     setupSubscriberKeys: function(container, keys) {
47309         var me = this,
47310             el = container.getFocusEl(),
47311             scope = keys.scope,
47312             handlers = {
47313                 backspace: me.focusLast,
47314                 enter: me.navigateIn,
47315                 esc: me.navigateOut,
47316                 scope: me
47317             },
47318
47319             navSiblings = function(e) {
47320                 if (me.focusedCmp === container) {
47321                     // Root the sibling navigation to this container, so that we
47322                     // can automatically dive into the container, rather than forcing
47323                     // the user to hit the enter key to dive in.
47324                     return me.navigateSiblings(e, me, container);
47325                 } else {
47326                     return me.navigateSiblings(e);
47327                 }
47328             };
47329
47330         Ext.iterate(keys, function(key, cb) {
47331             handlers[key] = function(e) {
47332                 var ret = navSiblings(e);
47333
47334                 if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
47335                     return true;
47336                 }
47337
47338                 return ret;
47339             };
47340         }, me);
47341
47342         return Ext.create('Ext.util.KeyNav', el, handlers);
47343     },
47344
47345     shouldShowFocusFrame: function(cmp) {
47346         var me = this,
47347             opts = me.options || {};
47348
47349         if (!me.focusFrame || !cmp) {
47350             return false;
47351         }
47352
47353         // Global trumps
47354         if (opts.focusFrame) {
47355             return true;
47356         }
47357
47358         if (me.focusData[cmp.id].focusFrame) {
47359             return true;
47360         }
47361
47362         return false;
47363     },
47364
47365     /**
47366      * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
47367      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
47368      * @param {Boolean/Object} options An object of the following options:
47369         - keys : Array/Object
47370             An array containing the string names of navigation keys to be supported. The allowed values are:
47371
47372             - 'left'
47373             - 'right'
47374             - 'up'
47375             - 'down'
47376
47377             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.:
47378
47379                 {
47380                     left: this.onLeftKey,
47381                     right: this.onRightKey,
47382                     scope: this
47383                 }
47384
47385         - focusFrame : Boolean (optional)
47386             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
47387      * @markdown
47388      */
47389     subscribe: function(container, options) {
47390         var me = this,
47391             EA = Ext.Array,
47392             data = {},
47393             subs = me.subscribers,
47394
47395             // Recursively add focus ability as long as a descendent container isn't
47396             // itself subscribed to the FocusManager, or else we'd have unwanted side
47397             // effects for subscribing a descendent container twice.
47398             safeSetFocus = function(cmp) {
47399                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
47400                     EA.forEach(cmp.query('>'), safeSetFocus);
47401                     me.setFocus(cmp, true, options);
47402                     cmp.on('add', data.onAdd, me);
47403                 } else if (!cmp.isContainer) {
47404                     me.setFocus(cmp, true, options);
47405                 }
47406             };
47407
47408         // We only accept containers
47409         if (!container || !container.isContainer) {
47410             return;
47411         }
47412
47413         if (!container.rendered) {
47414             container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
47415             return;
47416         }
47417
47418         // Init the DOM, incase this is the first time it will be used
47419         me.initDOM(options);
47420
47421         // Create key navigation for subscriber based on keys option
47422         data.keyNav = me.setupSubscriberKeys(container, options.keys);
47423
47424         // We need to keep track of components being added to our subscriber
47425         // and any containers nested deeply within it (omg), so let's do that.
47426         // Components that are removed are globally handled.
47427         // Also keep track of destruction of our container for auto-unsubscribe.
47428         data.onAdd = function(ct, cmp, idx) {
47429             safeSetFocus(cmp);
47430         };
47431         container.on('beforedestroy', me.unsubscribe, me);
47432
47433         // Now we setup focusing abilities for the container and all its components
47434         safeSetFocus(container);
47435
47436         // Add to our subscribers list
47437         subs.add(container.id, data);
47438     },
47439
47440     /**
47441      * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
47442      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
47443      * @markdown
47444      */
47445     unsubscribe: function(container) {
47446         var me = this,
47447             EA = Ext.Array,
47448             subs = me.subscribers,
47449             data,
47450
47451             // Recursively remove focus ability as long as a descendent container isn't
47452             // itself subscribed to the FocusManager, or else we'd have unwanted side
47453             // effects for unsubscribing an ancestor container.
47454             safeSetFocus = function(cmp) {
47455                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
47456                     EA.forEach(cmp.query('>'), safeSetFocus);
47457                     me.setFocus(cmp, false);
47458                     cmp.un('add', data.onAdd, me);
47459                 } else if (!cmp.isContainer) {
47460                     me.setFocus(cmp, false);
47461                 }
47462             };
47463
47464         if (!container || !subs.containsKey(container.id)) {
47465             return;
47466         }
47467
47468         data = subs.get(container.id);
47469         data.keyNav.destroy();
47470         container.un('beforedestroy', me.unsubscribe, me);
47471         subs.removeAtKey(container.id);
47472         safeSetFocus(container);
47473         me.removeDOM();
47474     }
47475 });
47476 /**
47477  * @class Ext.toolbar.Toolbar
47478  * @extends Ext.container.Container
47479
47480 Basic Toolbar class. Although the {@link Ext.container.Container#defaultType defaultType} for Toolbar is {@link Ext.button.Button button}, Toolbar 
47481 elements (child items for the Toolbar container) may be virtually any type of Component. Toolbar elements can be created explicitly via their 
47482 constructors, or implicitly via their xtypes, and can be {@link #add}ed dynamically.
47483
47484 __Some items have shortcut strings for creation:__
47485
47486 | Shortcut | xtype         | Class                         | Description                                        |
47487 |:---------|:--------------|:------------------------------|:---------------------------------------------------|
47488 | `->`     | `tbspacer`    | {@link Ext.toolbar.Fill}      | begin using the right-justified button container   |
47489 | `-`      | `tbseparator` | {@link Ext.toolbar.Separator} | add a vertical separator bar between toolbar items |
47490 | ` `      | `tbspacer`    | {@link Ext.toolbar.Spacer}    | add horiztonal space between elements              |
47491
47492 {@img Ext.toolbar.Toolbar/Ext.toolbar.Toolbar1.png Toolbar component}
47493 Example usage:
47494
47495     Ext.create('Ext.toolbar.Toolbar', {
47496         renderTo: document.body,
47497         width   : 500,
47498         items: [
47499             {
47500                 // xtype: 'button', // default for Toolbars
47501                 text: 'Button'
47502             },
47503             {
47504                 xtype: 'splitbutton',
47505                 text : 'Split Button'
47506             },
47507             // begin using the right-justified button container
47508             '->', // same as {xtype: 'tbfill'}, // Ext.toolbar.Fill
47509             {
47510                 xtype    : 'textfield',
47511                 name     : 'field1',
47512                 emptyText: 'enter search term'
47513             },
47514             // add a vertical separator bar between toolbar items
47515             '-', // same as {xtype: 'tbseparator'} to create Ext.toolbar.Separator
47516             'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.toolbar.TextItem
47517             {xtype: 'tbspacer'},// same as ' ' to create Ext.toolbar.Spacer
47518             'text 2',
47519             {xtype: 'tbspacer', width: 50}, // add a 50px space
47520             'text 3'
47521         ]
47522     });
47523
47524 Toolbars have {@link #enable} and {@link #disable} methods which when called, will enable/disable all items within your toolbar.
47525
47526 {@img Ext.toolbar.Toolbar/Ext.toolbar.Toolbar2.png Toolbar component}
47527 Example usage:
47528
47529     Ext.create('Ext.toolbar.Toolbar', {
47530         renderTo: document.body,
47531         width   : 400,
47532         items: [
47533             {
47534                 text: 'Button'
47535             },
47536             {
47537                 xtype: 'splitbutton',
47538                 text : 'Split Button'
47539             },
47540             '->',
47541             {
47542                 xtype    : 'textfield',
47543                 name     : 'field1',
47544                 emptyText: 'enter search term'
47545             }
47546         ]
47547     });
47548
47549 {@img Ext.toolbar.Toolbar/Ext.toolbar.Toolbar3.png Toolbar component}
47550 Example usage:
47551     
47552     var enableBtn = Ext.create('Ext.button.Button', {
47553         text    : 'Enable All Items',
47554         disabled: true,
47555         scope   : this,
47556         handler : function() {
47557             //disable the enable button and enable the disable button
47558             enableBtn.disable();
47559             disableBtn.enable();
47560             
47561             //enable the toolbar
47562             toolbar.enable();
47563         }
47564     });
47565     
47566     var disableBtn = Ext.create('Ext.button.Button', {
47567         text    : 'Disable All Items',
47568         scope   : this,
47569         handler : function() {
47570             //enable the enable button and disable button
47571             disableBtn.disable();
47572             enableBtn.enable();
47573             
47574             //disable the toolbar
47575             toolbar.disable();
47576         }
47577     });
47578     
47579     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
47580         renderTo: document.body,
47581         width   : 400,
47582         margin  : '5 0 0 0',
47583         items   : [enableBtn, disableBtn]
47584     });
47585
47586 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 
47587 which remove all items within the toolbar.
47588
47589 {@img Ext.toolbar.Toolbar/Ext.toolbar.Toolbar4.png Toolbar component}
47590 Example usage:
47591
47592     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
47593         renderTo: document.body,
47594         width   : 700,
47595         items: [
47596             {
47597                 text: 'Example Button'
47598             }
47599         ]
47600     });
47601     
47602     var addedItems = [];
47603     
47604     Ext.create('Ext.toolbar.Toolbar', {
47605         renderTo: document.body,
47606         width   : 700,
47607         margin  : '5 0 0 0',
47608         items   : [
47609             {
47610                 text   : 'Add a button',
47611                 scope  : this,
47612                 handler: function() {
47613                     var text = prompt('Please enter the text for your button:');
47614                     addedItems.push(toolbar.add({
47615                         text: text
47616                     }));
47617                 }
47618             },
47619             {
47620                 text   : 'Add a text item',
47621                 scope  : this,
47622                 handler: function() {
47623                     var text = prompt('Please enter the text for your item:');
47624                     addedItems.push(toolbar.add(text));
47625                 }
47626             },
47627             {
47628                 text   : 'Add a toolbar seperator',
47629                 scope  : this,
47630                 handler: function() {
47631                     addedItems.push(toolbar.add('-'));
47632                 }
47633             },
47634             {
47635                 text   : 'Add a toolbar spacer',
47636                 scope  : this,
47637                 handler: function() {
47638                     addedItems.push(toolbar.add('->'));
47639                 }
47640             },
47641             '->',
47642             {
47643                 text   : 'Remove last inserted item',
47644                 scope  : this,
47645                 handler: function() {
47646                     if (addedItems.length) {
47647                         toolbar.remove(addedItems.pop());
47648                     } else if (toolbar.items.length) {
47649                         toolbar.remove(toolbar.items.last());
47650                     } else {
47651                         alert('No items in the toolbar');
47652                     }
47653                 }
47654             },
47655             {
47656                 text   : 'Remove all items',
47657                 scope  : this,
47658                 handler: function() {
47659                     toolbar.removeAll();
47660                 }
47661             }
47662         ]
47663     });
47664
47665  * @constructor
47666  * Creates a new Toolbar
47667  * @param {Object/Array} config A config object or an array of buttons to <code>{@link #add}</code>
47668  * @xtype toolbar
47669  * @docauthor Robert Dougan <rob@sencha.com>
47670  * @markdown
47671  */
47672 Ext.define('Ext.toolbar.Toolbar', {
47673     extend: 'Ext.container.Container',
47674     requires: [
47675         'Ext.toolbar.Fill',
47676         'Ext.layout.container.HBox',
47677         'Ext.layout.container.VBox',
47678         'Ext.FocusManager'
47679     ],
47680     uses: [
47681         'Ext.toolbar.Separator'
47682     ],
47683     alias: 'widget.toolbar',
47684     alternateClassName: 'Ext.Toolbar',
47685     
47686     isToolbar: true,
47687     baseCls  : Ext.baseCSSPrefix + 'toolbar',
47688     ariaRole : 'toolbar',
47689     
47690     defaultType: 'button',
47691     
47692     /**
47693      * @cfg {Boolean} vertical
47694      * Set to `true` to make the toolbar vertical. The layout will become a `vbox`.
47695      * (defaults to `false`)
47696      */
47697     vertical: false,
47698
47699     /**
47700      * @cfg {String/Object} layout
47701      * This class assigns a default layout (<code>layout:'<b>hbox</b>'</code>).
47702      * Developers <i>may</i> override this configuration option if another layout
47703      * is required (the constructor must be passed a configuration object in this
47704      * case instead of an array).
47705      * See {@link Ext.container.Container#layout} for additional information.
47706      */
47707
47708     /**
47709      * @cfg {Boolean} enableOverflow
47710      * Defaults to false. Configure <code>true</code> to make the toolbar provide a button
47711      * which activates a dropdown Menu to show items which overflow the Toolbar's width.
47712      */
47713     enableOverflow: false,
47714     
47715     // private
47716     trackMenus: true,
47717     
47718     itemCls: Ext.baseCSSPrefix + 'toolbar-item',
47719     
47720     initComponent: function() {
47721         var me = this,
47722             keys;
47723
47724         // check for simplified (old-style) overflow config:
47725         if (!me.layout && me.enableOverflow) {
47726             me.layout = { overflowHandler: 'Menu' };
47727         }
47728         
47729         if (me.dock === 'right' || me.dock === 'left') {
47730             me.vertical = true;
47731         }
47732
47733         me.layout = Ext.applyIf(Ext.isString(me.layout) ? {
47734             type: me.layout
47735         } : me.layout || {}, {
47736             type: me.vertical ? 'vbox' : 'hbox',
47737             align: me.vertical ? 'stretchmax' : 'middle'
47738         });
47739         
47740         if (me.vertical) {
47741             me.addClsWithUI('vertical');
47742         }
47743         
47744         // @TODO: remove this hack and implement a more general solution
47745         if (me.ui === 'footer') {
47746             me.ignoreBorderManagement = true;
47747         }
47748         
47749         me.callParent();
47750
47751         /**
47752          * @event overflowchange
47753          * Fires after the overflow state has changed.
47754          * @param {Object} c The Container
47755          * @param {Boolean} lastOverflow overflow state
47756          */
47757         me.addEvents('overflowchange');
47758         
47759         // Subscribe to Ext.FocusManager for key navigation
47760         keys = me.vertical ? ['up', 'down'] : ['left', 'right'];
47761         Ext.FocusManager.subscribe(me, {
47762             keys: keys
47763         });
47764     },
47765
47766     /**
47767      * <p>Adds element(s) to the toolbar -- this function takes a variable number of
47768      * arguments of mixed type and adds them to the toolbar.</p>
47769      * <br><p><b>Note</b>: See the notes within {@link Ext.container.Container#add}.</p>
47770      * @param {Mixed} arg1 The following types of arguments are all valid:<br />
47771      * <ul>
47772      * <li>{@link Ext.button.Button} config: A valid button config object (equivalent to {@link #addButton})</li>
47773      * <li>HtmlElement: Any standard HTML element (equivalent to {@link #addElement})</li>
47774      * <li>Field: Any form field (equivalent to {@link #addField})</li>
47775      * <li>Item: Any subclass of {@link Ext.toolbar.Item} (equivalent to {@link #addItem})</li>
47776      * <li>String: Any generic string (gets wrapped in a {@link Ext.toolbar.TextItem}, equivalent to {@link #addText}).
47777      * Note that there are a few special strings that are treated differently as explained next.</li>
47778      * <li>'-': Creates a separator element (equivalent to {@link #addSeparator})</li>
47779      * <li>' ': Creates a spacer element (equivalent to {@link #addSpacer})</li>
47780      * <li>'->': Creates a fill element (equivalent to {@link #addFill})</li>
47781      * </ul>
47782      * @param {Mixed} arg2
47783      * @param {Mixed} etc.
47784      * @method add
47785      */
47786
47787     // private
47788     lookupComponent: function(c) {
47789         if (Ext.isString(c)) {
47790             var shortcut = Ext.toolbar.Toolbar.shortcuts[c];
47791             if (shortcut) {
47792                 c = {
47793                     xtype: shortcut
47794                 };
47795             } else {
47796                 c = {
47797                     xtype: 'tbtext',
47798                     text: c
47799                 };
47800             }
47801             this.applyDefaults(c);
47802         }
47803         return this.callParent(arguments);
47804     },
47805
47806     // private
47807     applyDefaults: function(c) {
47808         if (!Ext.isString(c)) {
47809             c = this.callParent(arguments);
47810             var d = this.internalDefaults;
47811             if (c.events) {
47812                 Ext.applyIf(c.initialConfig, d);
47813                 Ext.apply(c, d);
47814             } else {
47815                 Ext.applyIf(c, d);
47816             }
47817         }
47818         return c;
47819     },
47820
47821     // private
47822     trackMenu: function(item, remove) {
47823         if (this.trackMenus && item.menu) {
47824             var method = remove ? 'mun' : 'mon',
47825                 me = this;
47826
47827             me[method](item, 'menutriggerover', me.onButtonTriggerOver, me);
47828             me[method](item, 'menushow', me.onButtonMenuShow, me);
47829             me[method](item, 'menuhide', me.onButtonMenuHide, me);
47830         }
47831     },
47832
47833     // private
47834     constructButton: function(item) {
47835         return item.events ? item : this.createComponent(item, item.split ? 'splitbutton' : this.defaultType);
47836     },
47837
47838     // private
47839     onBeforeAdd: function(component) {
47840         if (component.is('field') || (component.is('button') && this.ui != 'footer')) {
47841             component.ui = component.ui + '-toolbar';
47842         }
47843         
47844         // Any separators needs to know if is vertical or not
47845         if (component instanceof Ext.toolbar.Separator) {
47846             component.setUI((this.vertical) ? 'vertical' : 'horizontal');
47847         }
47848         
47849         this.callParent(arguments);
47850     },
47851
47852     // private
47853     onAdd: function(component) {
47854         this.callParent(arguments);
47855
47856         this.trackMenu(component);
47857         if (this.disabled) {
47858             component.disable();
47859         }
47860     },
47861
47862     // private
47863     onRemove: function(c) {
47864         this.callParent(arguments);
47865         this.trackMenu(c, true);
47866     },
47867
47868     // private
47869     onButtonTriggerOver: function(btn){
47870         if (this.activeMenuBtn && this.activeMenuBtn != btn) {
47871             this.activeMenuBtn.hideMenu();
47872             btn.showMenu();
47873             this.activeMenuBtn = btn;
47874         }
47875     },
47876
47877     // private
47878     onButtonMenuShow: function(btn) {
47879         this.activeMenuBtn = btn;
47880     },
47881
47882     // private
47883     onButtonMenuHide: function(btn) {
47884         delete this.activeMenuBtn;
47885     }
47886 }, function() {
47887     this.shortcuts = {
47888         '-' : 'tbseparator',
47889         ' ' : 'tbspacer',
47890         '->': 'tbfill'
47891     };
47892 });
47893 /**
47894  * @class Ext.panel.AbstractPanel
47895  * @extends Ext.container.Container
47896  * <p>A base class which provides methods common to Panel classes across the Sencha product range.</p>
47897  * <p>Please refer to sub class's documentation</p>
47898  * @constructor
47899  * @param {Object} config The config object
47900  */
47901 Ext.define('Ext.panel.AbstractPanel', {
47902
47903     /* Begin Definitions */
47904
47905     extend: 'Ext.container.Container',
47906
47907     requires: ['Ext.util.MixedCollection', 'Ext.core.Element', 'Ext.toolbar.Toolbar'],
47908
47909     /* End Definitions */
47910
47911     /**
47912      * @cfg {String} baseCls
47913      * The base CSS class to apply to this panel's element (defaults to <code>'x-panel'</code>).
47914      */
47915     baseCls : Ext.baseCSSPrefix + 'panel',
47916
47917     /**
47918      * @cfg {Number/String} bodyPadding
47919      * A shortcut for setting a padding style on the body element. The value can either be
47920      * a number to be applied to all sides, or a normal css string describing padding.
47921      * Defaults to <code>undefined</code>.
47922      */
47923
47924     /**
47925      * @cfg {Boolean} bodyBorder
47926      * A shortcut to add or remove the border on the body of a panel. This only applies to a panel which has the {@link #frame} configuration set to `true`.
47927      * Defaults to <code>undefined</code>.
47928      */
47929     
47930     /**
47931      * @cfg {String/Object/Function} bodyStyle
47932      * Custom CSS styles to be applied to the panel's body element, which can be supplied as a valid CSS style string,
47933      * an object containing style property name/value pairs or a function that returns such a string or object.
47934      * For example, these two formats are interpreted to be equivalent:<pre><code>
47935 bodyStyle: 'background:#ffc; padding:10px;'
47936
47937 bodyStyle: {
47938     background: '#ffc',
47939     padding: '10px'
47940 }
47941      * </code></pre>
47942      */
47943     
47944     /**
47945      * @cfg {String/Array} bodyCls
47946      * A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
47947      * The following examples are all valid:<pre><code>
47948 bodyCls: 'foo'
47949 bodyCls: 'foo bar'
47950 bodyCls: ['foo', 'bar']
47951      * </code></pre>
47952      */
47953
47954     isPanel: true,
47955
47956     componentLayout: 'dock',
47957
47958     renderTpl: ['<div class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl> {baseCls}-body-{ui}<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl></tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>'],
47959
47960     // TODO: Move code examples into product-specific files. The code snippet below is Touch only.
47961     /**
47962      * @cfg {Object/Array} dockedItems
47963      * A component or series of components to be added as docked items to this panel.
47964      * The docked items can be docked to either the top, right, left or bottom of a panel.
47965      * This is typically used for things like toolbars or tab bars:
47966      * <pre><code>
47967 var panel = new Ext.panel.Panel({
47968     fullscreen: true,
47969     dockedItems: [{
47970         xtype: 'toolbar',
47971         dock: 'top',
47972         items: [{
47973             text: 'Docked to the top'
47974         }]
47975     }]
47976 });</code></pre>
47977      */
47978      
47979     border: true,
47980
47981     initComponent : function() {
47982         var me = this;
47983         
47984         me.addEvents(
47985             /**
47986              * @event bodyresize
47987              * Fires after the Panel has been resized.
47988              * @param {Ext.panel.Panel} p the Panel which has been resized.
47989              * @param {Number} width The Panel body's new width.
47990              * @param {Number} height The Panel body's new height.
47991              */
47992             'bodyresize'
47993             // // inherited
47994             // 'activate',
47995             // // inherited
47996             // 'deactivate'
47997         );
47998
47999         Ext.applyIf(me.renderSelectors, {
48000             body: '.' + me.baseCls + '-body'
48001         });
48002         
48003         //!frame 
48004         //!border
48005         
48006         if (me.frame && me.border && me.bodyBorder === undefined) {
48007             me.bodyBorder = false;
48008         }
48009         if (me.frame && me.border && (me.bodyBorder === false || me.bodyBorder === 0)) {
48010             me.manageBodyBorders = true;
48011         }
48012         
48013         me.callParent();
48014     },
48015
48016     // @private
48017     initItems : function() {
48018         var me = this,
48019             items = me.dockedItems;
48020             
48021         me.callParent();
48022         me.dockedItems = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
48023         if (items) {
48024             me.addDocked(items);
48025         }
48026     },
48027
48028     /**
48029      * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
48030      * @param {String/Number} comp The id, itemId or position of the docked component (see {@link #getComponent} for details)
48031      * @return {Ext.Component} The docked component (if found)
48032      */
48033     getDockedComponent: function(comp) {
48034         if (Ext.isObject(comp)) {
48035             comp = comp.getItemId();
48036         }
48037         return this.dockedItems.get(comp);
48038     },
48039
48040     /**
48041      * Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
48042      * items, the dockedItems are searched and the matched component (if any) returned (see {@loink #getDockedComponent}). Note that docked
48043      * items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
48044      * @param {String/Number} comp The component id, itemId or position to find
48045      * @return {Ext.Component} The component (if found)
48046      */
48047     getComponent: function(comp) {
48048         var component = this.callParent(arguments);
48049         if (component === undefined && !Ext.isNumber(comp)) {
48050             // If the arg is a numeric index skip docked items
48051             component = this.getDockedComponent(comp);
48052         }
48053         return component;
48054     },
48055
48056     /**
48057      * Parses the {@link bodyStyle} config if available to create a style string that will be applied to the body element.
48058      * This also includes {@link bodyPadding} and {@link bodyBorder} if available.
48059      * @return {String} A CSS style string with body styles, padding and border.
48060      * @private
48061      */
48062     initBodyStyles: function() {
48063         var me = this,
48064             bodyStyle = me.bodyStyle,
48065             styles = [],
48066             Element = Ext.core.Element,
48067             prop;
48068
48069         if (Ext.isFunction(bodyStyle)) {
48070             bodyStyle = bodyStyle();
48071         }
48072         if (Ext.isString(bodyStyle)) {
48073             styles = bodyStyle.split(';');
48074         } else {
48075             for (prop in bodyStyle) {
48076                 if (bodyStyle.hasOwnProperty(prop)) {
48077                     styles.push(prop + ':' + bodyStyle[prop]);
48078                 }
48079             }
48080         }
48081
48082         if (me.bodyPadding !== undefined) {
48083             styles.push('padding: ' + Element.unitizeBox((me.bodyPadding === true) ? 5 : me.bodyPadding));
48084         }
48085         if (me.frame && me.bodyBorder) {
48086             if (!Ext.isNumber(me.bodyBorder)) {
48087                 me.bodyBorder = 1;
48088             }
48089             styles.push('border-width: ' + Element.unitizeBox(me.bodyBorder));
48090         }
48091         delete me.bodyStyle;
48092         return styles.length ? styles.join(';') : undefined;
48093     },
48094     
48095     /**
48096      * Parse the {@link bodyCls} config if available to create a comma-delimited string of 
48097      * CSS classes to be applied to the body element.
48098      * @return {String} The CSS class(es)
48099      * @private
48100      */
48101     initBodyCls: function() {
48102         var me = this,
48103             cls = '',
48104             bodyCls = me.bodyCls;
48105         
48106         if (bodyCls) {
48107             Ext.each(bodyCls, function(v) {
48108                 cls += " " + v;
48109             });
48110             delete me.bodyCls;
48111         }
48112         return cls.length > 0 ? cls : undefined;
48113     },
48114     
48115     /**
48116      * Initialized the renderData to be used when rendering the renderTpl.
48117      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
48118      * @private
48119      */
48120     initRenderData: function() {
48121         return Ext.applyIf(this.callParent(), {
48122             bodyStyle: this.initBodyStyles(),
48123             bodyCls: this.initBodyCls()
48124         });
48125     },
48126
48127     /**
48128      * Adds docked item(s) to the panel.
48129      * @param {Object/Array} component The Component or array of components to add. The components
48130      * must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
48131      * 'bottom', 'left').
48132      * @param {Number} pos (optional) The index at which the Component will be added
48133      */
48134     addDocked : function(items, pos) {
48135         var me = this,
48136             i = 0,
48137             item, length;
48138
48139         items = me.prepareItems(items);
48140         length = items.length;
48141
48142         for (; i < length; i++) {
48143             item = items[i];
48144             item.dock = item.dock || 'top';
48145
48146             // Allow older browsers to target docked items to style without borders
48147             if (me.border === false) {
48148                 // item.cls = item.cls || '' + ' ' + me.baseCls + '-noborder-docked-' + item.dock;
48149             }
48150
48151             if (pos !== undefined) {
48152                 me.dockedItems.insert(pos + i, item);
48153             }
48154             else {
48155                 me.dockedItems.add(item);
48156             }
48157             item.onAdded(me, i);
48158             me.onDockedAdd(item);
48159         }
48160         if (me.rendered && !me.suspendLayout) {
48161             me.doComponentLayout();
48162         }
48163         return items;
48164     },
48165
48166     // Placeholder empty functions
48167     onDockedAdd : Ext.emptyFn,
48168     onDockedRemove : Ext.emptyFn,
48169
48170     /**
48171      * Inserts docked item(s) to the panel at the indicated position.
48172      * @param {Number} pos The index at which the Component will be inserted
48173      * @param {Object/Array} component. The Component or array of components to add. The components
48174      * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
48175      * 'bottom', 'left').
48176      */
48177     insertDocked : function(pos, items) {
48178         this.addDocked(items, pos);
48179     },
48180
48181     /**
48182      * Removes the docked item from the panel.
48183      * @param {Ext.Component} item. The Component to remove.
48184      * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
48185      */
48186     removeDocked : function(item, autoDestroy) {
48187         var me = this,
48188             layout,
48189             hasLayout;
48190             
48191         if (!me.dockedItems.contains(item)) {
48192             return item;
48193         }
48194
48195         layout = me.componentLayout;
48196         hasLayout = layout && me.rendered;
48197
48198         if (hasLayout) {
48199             layout.onRemove(item);
48200         }
48201
48202         me.dockedItems.remove(item);
48203         item.onRemoved();
48204         me.onDockedRemove(item);
48205
48206         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
48207             item.destroy();
48208         }
48209
48210         if (hasLayout && !autoDestroy) {
48211             layout.afterRemove(item);
48212         }
48213         
48214         if (!this.destroying) {
48215             me.doComponentLayout();
48216         }
48217
48218         return item;
48219     },
48220
48221     /**
48222      * Retrieve an array of all currently docked Components.
48223      * @param {String} cqSelector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
48224      * @return {Array} An array of components.
48225      */
48226     getDockedItems : function(cqSelector) {
48227         var me = this,
48228             // Start with a weight of 1, so users can provide <= 0 to come before top items
48229             // Odd numbers, so users can provide a weight to come in between if desired
48230             defaultWeight = { top: 1, left: 3, right: 5, bottom: 7 },
48231             dockedItems;
48232
48233         if (me.dockedItems && me.dockedItems.items.length) {
48234             // Allow filtering of returned docked items by CQ selector.
48235             if (cqSelector) {
48236                 dockedItems = Ext.ComponentQuery.query(cqSelector, me.dockedItems.items);
48237             } else {
48238                 dockedItems = me.dockedItems.items.slice();
48239             }
48240
48241             Ext.Array.sort(dockedItems, function(a, b) {
48242                 // Docked items are ordered by their visual representation by default (t,l,r,b)
48243                 // TODO: Enforce position ordering, and have weights be sub-ordering within positions?
48244                 var aw = a.weight || defaultWeight[a.dock],
48245                     bw = b.weight || defaultWeight[b.dock];
48246                 if (Ext.isNumber(aw) && Ext.isNumber(bw)) {
48247                     return aw - bw;
48248                 }
48249                 return 0;
48250             });
48251             
48252             return dockedItems;
48253         }
48254         return [];
48255     },
48256     
48257     // inherit docs
48258     addUIClsToElement: function(cls, force) {
48259         var me = this;
48260         
48261         me.callParent(arguments);
48262         
48263         if (!force && me.rendered) {
48264             me.body.addCls(Ext.baseCSSPrefix + cls);
48265             me.body.addCls(me.baseCls + '-body-' + cls);
48266             me.body.addCls(me.baseCls + '-body-' + me.ui + '-' + cls);
48267         }
48268     },
48269     
48270     // inherit docs
48271     removeUIClsFromElement: function(cls, force) {
48272         var me = this;
48273         
48274         me.callParent(arguments);
48275         
48276         if (!force && me.rendered) {
48277             me.body.removeCls(Ext.baseCSSPrefix + cls);
48278             me.body.removeCls(me.baseCls + '-body-' + cls);
48279             me.body.removeCls(me.baseCls + '-body-' + me.ui + '-' + cls);
48280         }
48281     },
48282     
48283     // inherit docs
48284     addUIToElement: function(force) {
48285         var me = this;
48286         
48287         me.callParent(arguments);
48288         
48289         if (!force && me.rendered) {
48290             me.body.addCls(me.baseCls + '-body-' + me.ui);
48291         }
48292     },
48293     
48294     // inherit docs
48295     removeUIFromElement: function() {
48296         var me = this;
48297         
48298         me.callParent(arguments);
48299         
48300         if (me.rendered) {
48301             me.body.removeCls(me.baseCls + '-body-' + me.ui);
48302         }
48303     },
48304
48305     // @private
48306     getTargetEl : function() {
48307         return this.body;
48308     },
48309
48310     getRefItems: function(deep) {
48311         var items = this.callParent(arguments),
48312             // deep fetches all docked items, and their descendants using '*' selector and then '* *'
48313             dockedItems = this.getDockedItems(deep ? '*,* *' : undefined),
48314             ln = dockedItems.length,
48315             i = 0,
48316             item;
48317         
48318         // Find the index where we go from top/left docked items to right/bottom docked items
48319         for (; i < ln; i++) {
48320             item = dockedItems[i];
48321             if (item.dock === 'right' || item.dock === 'bottom') {
48322                 break;
48323             }
48324         }
48325         
48326         // Return docked items in the top/left position before our container items, and
48327         // return right/bottom positioned items after our container items.
48328         // See AbstractDock.renderItems() for more information.
48329         return dockedItems.splice(0, i).concat(items).concat(dockedItems);
48330     },
48331
48332     beforeDestroy: function(){
48333         var docked = this.dockedItems,
48334             c;
48335
48336         if (docked) {
48337             while ((c = docked.first())) {
48338                 this.removeDocked(c, true);
48339             }
48340         }
48341         this.callParent();
48342     },
48343     
48344     setBorder: function(border) {
48345         var me = this;
48346         me.border = (border !== undefined) ? border : true;
48347         if (me.rendered) {
48348             me.doComponentLayout();
48349         }
48350     }
48351 });
48352 /**
48353  * @class Ext.panel.Header
48354  * @extends Ext.container.Container
48355  * Simple header class which is used for on {@link Ext.panel.Panel} and {@link Ext.window.Window}
48356  * @xtype header
48357  */
48358 Ext.define('Ext.panel.Header', {
48359     extend: 'Ext.container.Container',
48360     uses: ['Ext.panel.Tool', 'Ext.draw.Component', 'Ext.util.CSS'],
48361     alias: 'widget.header',
48362
48363     isHeader       : true,
48364     defaultType    : 'tool',
48365     indicateDrag   : false,
48366     weight         : -1,
48367
48368     renderTpl: ['<div class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl><tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl></tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>'],
48369
48370     initComponent: function() {
48371         var me = this,
48372             rule,
48373             style,
48374             titleTextEl,
48375             ui;
48376
48377         me.indicateDragCls = me.baseCls + '-draggable';
48378         me.title = me.title || '&#160;';
48379         me.tools = me.tools || [];
48380         me.items = me.items || [];
48381         me.orientation = me.orientation || 'horizontal';
48382         me.dock = (me.dock) ? me.dock : (me.orientation == 'horizontal') ? 'top' : 'left';
48383
48384         //add the dock as a ui
48385         //this is so we support top/right/left/bottom headers
48386         me.addClsWithUI(me.orientation);
48387         me.addClsWithUI(me.dock);
48388
48389         Ext.applyIf(me.renderSelectors, {
48390             body: '.' + me.baseCls + '-body'
48391         });
48392
48393         // Add Icon
48394         if (!Ext.isEmpty(me.iconCls)) {
48395             me.initIconCmp();
48396             me.items.push(me.iconCmp);
48397         }
48398
48399         // Add Title
48400         if (me.orientation == 'vertical') {
48401             // Hack for IE6/7's inability to display an inline-block
48402             if (Ext.isIE6 || Ext.isIE7) {
48403                 me.width = this.width || 24;
48404             } else if (Ext.isIEQuirks) {
48405                 me.width = this.width || 25;
48406             }
48407
48408             me.layout = {
48409                 type : 'vbox',
48410                 align: 'center',
48411                 clearInnerCtOnLayout: true,
48412                 bindToOwnerCtContainer: false
48413             };
48414             me.textConfig = {
48415                 cls: me.baseCls + '-text',
48416                 type: 'text',
48417                 text: me.title,
48418                 rotate: {
48419                     degrees: 90
48420                 }
48421             };
48422             ui = me.ui;
48423             if (Ext.isArray(ui)) {
48424                 ui = ui[0];
48425             }
48426             rule = Ext.util.CSS.getRule('.' + me.baseCls + '-text-' + ui);
48427             if (rule) {
48428                 style = rule.style;
48429             }
48430             if (style) {
48431                 Ext.apply(me.textConfig, {
48432                     'font-family': style.fontFamily,
48433                     'font-weight': style.fontWeight,
48434                     'font-size': style.fontSize,
48435                     fill: style.color
48436                 });
48437             }
48438             me.titleCmp = Ext.create('Ext.draw.Component', {
48439                 ariaRole  : 'heading',
48440                 focusable: false,
48441                 viewBox: false,
48442                 autoSize: true,
48443                 margins: '5 0 0 0',
48444                 items: [ me.textConfig ],
48445                 renderSelectors: {
48446                     textEl: '.' + me.baseCls + '-text'
48447                 }
48448             });
48449         } else {
48450             me.layout = {
48451                 type : 'hbox',
48452                 align: 'middle',
48453                 clearInnerCtOnLayout: true,
48454                 bindToOwnerCtContainer: false
48455             };
48456             me.titleCmp = Ext.create('Ext.Component', {
48457                 xtype     : 'component',
48458                 ariaRole  : 'heading',
48459                 focusable: false,
48460                 renderTpl : ['<span class="{cls}-text {cls}-text-{ui}">{title}</span>'],
48461                 renderData: {
48462                     title: me.title,
48463                     cls  : me.baseCls,
48464                     ui   : me.ui
48465                 },
48466                 renderSelectors: {
48467                     textEl: '.' + me.baseCls + '-text'
48468                 }
48469             });
48470         }
48471         me.items.push(me.titleCmp);
48472
48473         // Spacer ->
48474         me.items.push({
48475             xtype: 'component',
48476             html : '&nbsp;',
48477             flex : 1,
48478             focusable: false
48479         });
48480
48481         // Add Tools
48482         me.items = me.items.concat(me.tools);
48483         this.callParent();
48484     },
48485
48486     initIconCmp: function() {
48487         this.iconCmp = Ext.create('Ext.Component', {
48488             focusable: false,
48489             renderTpl : ['<img alt="" src="{blank}" class="{cls}-icon {iconCls}"/>'],
48490             renderData: {
48491                 blank  : Ext.BLANK_IMAGE_URL,
48492                 cls    : this.baseCls,
48493                 iconCls: this.iconCls,
48494                 orientation: this.orientation
48495             },
48496             renderSelectors: {
48497                 iconEl: '.' + this.baseCls + '-icon'
48498             },
48499             iconCls: this.iconCls
48500         });
48501     },
48502
48503     afterRender: function() {
48504         var me = this;
48505
48506         me.el.unselectable();
48507         if (me.indicateDrag) {
48508             me.el.addCls(me.indicateDragCls);
48509         }
48510         me.mon(me.el, {
48511             click: me.onClick,
48512             scope: me
48513         });
48514         me.callParent();
48515     },
48516
48517     afterLayout: function() {
48518         var me = this;
48519         me.callParent(arguments);
48520
48521         // IE7 needs a forced repaint to make the top framing div expand to full width
48522         if (Ext.isIE7) {
48523             me.el.repaint();
48524         }
48525     },
48526
48527     // inherit docs
48528     addUIClsToElement: function(cls, force) {
48529         var me = this;
48530
48531         me.callParent(arguments);
48532
48533         if (!force && me.rendered) {
48534             me.body.addCls(me.baseCls + '-body-' + cls);
48535             me.body.addCls(me.baseCls + '-body-' + me.ui + '-' + cls);
48536         }
48537     },
48538
48539     // inherit docs
48540     removeUIClsFromElement: function(cls, force) {
48541         var me = this;
48542
48543         me.callParent(arguments);
48544
48545         if (!force && me.rendered) {
48546             me.body.removeCls(me.baseCls + '-body-' + cls);
48547             me.body.removeCls(me.baseCls + '-body-' + me.ui + '-' + cls);
48548         }
48549     },
48550
48551     // inherit docs
48552     addUIToElement: function(force) {
48553         var me = this;
48554
48555         me.callParent(arguments);
48556
48557         if (!force && me.rendered) {
48558             me.body.addCls(me.baseCls + '-body-' + me.ui);
48559         }
48560
48561         if (!force && me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
48562             me.titleCmp.textEl.addCls(me.baseCls + '-text-' + me.ui);
48563         }
48564     },
48565
48566     // inherit docs
48567     removeUIFromElement: function() {
48568         var me = this;
48569
48570         me.callParent(arguments);
48571
48572         if (me.rendered) {
48573             me.body.removeCls(me.baseCls + '-body-' + me.ui);
48574         }
48575
48576         if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
48577             me.titleCmp.textEl.removeCls(me.baseCls + '-text-' + me.ui);
48578         }
48579     },
48580
48581     onClick: function(e) {
48582         if (!e.getTarget(Ext.baseCSSPrefix + 'tool')) {
48583             this.fireEvent('click', e);
48584         }
48585     },
48586
48587     getTargetEl: function() {
48588         return this.body || this.frameBody || this.el;
48589     },
48590
48591     /**
48592      * Sets the title of the header.
48593      * @param {String} title The title to be set
48594      */
48595     setTitle: function(title) {
48596         var me = this;
48597         if (me.rendered) {
48598             if (me.titleCmp.rendered) {
48599                 if (me.titleCmp.surface) {
48600                     me.title = title || '';
48601                     var sprite = me.titleCmp.surface.items.items[0],
48602                         surface = me.titleCmp.surface;
48603
48604                     surface.remove(sprite);
48605                     me.textConfig.type = 'text';
48606                     me.textConfig.text = title;
48607                     sprite = surface.add(me.textConfig);
48608                     sprite.setAttributes({
48609                         rotate: {
48610                             degrees: 90
48611                         }
48612                     }, true);
48613                     me.titleCmp.autoSizeSurface();
48614                 } else {
48615                     me.title = title || '&#160;';
48616                     me.titleCmp.textEl.update(me.title);
48617                 }
48618             } else {
48619                 me.titleCmp.on({
48620                     render: function() {
48621                         me.setTitle(title);
48622                     },
48623                     single: true
48624                 });
48625             }
48626         } else {
48627             me.on({
48628                 render: function() {
48629                     me.layout.layout();
48630                     me.setTitle(title);
48631                 },
48632                 single: true
48633             });
48634         }
48635     },
48636
48637     /**
48638      * Sets the CSS class that provides the icon image for this panel.  This method will replace any existing
48639      * icon class if one has already been set and fire the {@link #iconchange} event after completion.
48640      * @param {String} cls The new CSS class name
48641      */
48642     setIconCls: function(cls) {
48643         this.iconCls = cls;
48644         if (!this.iconCmp) {
48645             this.initIconCmp();
48646             this.insert(0, this.iconCmp);
48647         }
48648         else {
48649             if (!cls || !cls.length) {
48650                 this.iconCmp.destroy();
48651             }
48652             else {
48653                 var iconCmp = this.iconCmp,
48654                     el      = iconCmp.iconEl;
48655
48656                 el.removeCls(iconCmp.iconCls);
48657                 el.addCls(cls);
48658                 iconCmp.iconCls = cls;
48659             }
48660         }
48661     },
48662
48663     /**
48664      * Add a tool to the header
48665      * @param {Object} tool
48666      */
48667     addTool: function(tool) {
48668         this.tools.push(this.add(tool));
48669     },
48670
48671     /**
48672      * @private
48673      * Set up the tools.&lt;tool type> link in the owning Panel.
48674      * Bind the tool to its owning Panel.
48675      * @param component
48676      * @param index
48677      */
48678     onAdd: function(component, index) {
48679         this.callParent([arguments]);
48680         if (component instanceof Ext.panel.Tool) {
48681             component.bindTo(this.ownerCt);
48682             this.tools[component.type] = component;
48683         }
48684     }
48685 });
48686
48687 /**
48688  * @class Ext.fx.target.Element
48689  * @extends Ext.fx.target.Target
48690  * 
48691  * This class represents a animation target for an {@link Ext.core.Element}. In general this class will not be
48692  * created directly, the {@link Ext.core.Element} will be passed to the animation and
48693  * and the appropriate target will be created.
48694  */
48695 Ext.define('Ext.fx.target.Element', {
48696
48697     /* Begin Definitions */
48698     
48699     extend: 'Ext.fx.target.Target',
48700     
48701     /* End Definitions */
48702
48703     type: 'element',
48704
48705     getElVal: function(el, attr, val) {
48706         if (val == undefined) {
48707             if (attr === 'x') {
48708                 val = el.getX();
48709             }
48710             else if (attr === 'y') {
48711                 val = el.getY();
48712             }
48713             else if (attr === 'scrollTop') {
48714                 val = el.getScroll().top;
48715             }
48716             else if (attr === 'scrollLeft') {
48717                 val = el.getScroll().left;
48718             }
48719             else if (attr === 'height') {
48720                 val = el.getHeight();
48721             }
48722             else if (attr === 'width') {
48723                 val = el.getWidth();
48724             }
48725             else {
48726                 val = el.getStyle(attr);
48727             }
48728         }
48729         return val;
48730     },
48731
48732     getAttr: function(attr, val) {
48733         var el = this.target;
48734         return [[ el, this.getElVal(el, attr, val)]];
48735     },
48736
48737     setAttr: function(targetData) {
48738         var target = this.target,
48739             ln = targetData.length,
48740             attrs, attr, o, i, j, ln2, element, value;
48741         for (i = 0; i < ln; i++) {
48742             attrs = targetData[i].attrs;
48743             for (attr in attrs) {
48744                 if (attrs.hasOwnProperty(attr)) {
48745                     ln2 = attrs[attr].length;
48746                     for (j = 0; j < ln2; j++) {
48747                         o = attrs[attr][j];
48748                         element = o[0];
48749                         value = o[1];
48750                         if (attr === 'x') {
48751                             element.setX(value);
48752                         }
48753                         else if (attr === 'y') {
48754                             element.setY(value);
48755                         }
48756                         else if (attr === 'scrollTop') {
48757                             element.scrollTo('top', value);
48758                         }
48759                         else if (attr === 'scrollLeft') {
48760                             element.scrollTo('left',value);
48761                         }
48762                         else {
48763                             element.setStyle(attr, value);
48764                         }
48765                     }
48766                 }
48767             }
48768         }
48769     }
48770 });
48771
48772 /**
48773  * @class Ext.fx.target.CompositeElement
48774  * @extends Ext.fx.target.Element
48775  * 
48776  * This class represents a animation target for a {@link Ext.CompositeElement}. It allows
48777  * each {@link Ext.core.Element} in the group to be animated as a whole. In general this class will not be
48778  * created directly, the {@link Ext.CompositeElement} will be passed to the animation and
48779  * and the appropriate target will be created.
48780  */
48781 Ext.define('Ext.fx.target.CompositeElement', {
48782
48783     /* Begin Definitions */
48784
48785     extend: 'Ext.fx.target.Element',
48786
48787     /* End Definitions */
48788
48789     isComposite: true,
48790     
48791     constructor: function(target) {
48792         target.id = target.id || Ext.id(null, 'ext-composite-');
48793         this.callParent([target]);
48794     },
48795
48796     getAttr: function(attr, val) {
48797         var out = [],
48798             target = this.target;
48799         target.each(function(el) {
48800             out.push([el, this.getElVal(el, attr, val)]);
48801         }, this);
48802         return out;
48803     }
48804 });
48805
48806 /**
48807  * @class Ext.fx.Manager
48808  * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
48809  * @private
48810  * @singleton
48811  */
48812
48813 Ext.define('Ext.fx.Manager', {
48814
48815     /* Begin Definitions */
48816
48817     singleton: true,
48818
48819     requires: ['Ext.util.MixedCollection',
48820                'Ext.fx.target.Element',
48821                'Ext.fx.target.CompositeElement',
48822                'Ext.fx.target.Sprite',
48823                'Ext.fx.target.CompositeSprite',
48824                'Ext.fx.target.Component'],
48825
48826     mixins: {
48827         queue: 'Ext.fx.Queue'
48828     },
48829
48830     /* End Definitions */
48831
48832     constructor: function() {
48833         this.items = Ext.create('Ext.util.MixedCollection');
48834         this.mixins.queue.constructor.call(this);
48835
48836         // this.requestAnimFrame = (function() {
48837         //     var raf = window.requestAnimationFrame ||
48838         //               window.webkitRequestAnimationFrame ||
48839         //               window.mozRequestAnimationFrame ||
48840         //               window.oRequestAnimationFrame ||
48841         //               window.msRequestAnimationFrame;
48842         //     if (raf) {
48843         //         return function(callback, element) {
48844         //             raf(callback);
48845         //         };
48846         //     }
48847         //     else {
48848         //         return function(callback, element) {
48849         //             window.setTimeout(callback, Ext.fx.Manager.interval);
48850         //         };
48851         //     }
48852         // })();
48853     },
48854
48855     /**
48856      * @cfg {Number} interval Default interval in miliseconds to calculate each frame.  Defaults to 16ms (~60fps)
48857      */
48858     interval: 16,
48859
48860     /**
48861      * @cfg {Boolean} forceJS Turn off to not use CSS3 transitions when they are available
48862      */
48863     forceJS: true,
48864
48865     // @private Target factory
48866     createTarget: function(target) {
48867         var me = this,
48868             useCSS3 = !me.forceJS && Ext.supports.Transitions,
48869             targetObj;
48870
48871         me.useCSS3 = useCSS3;
48872
48873         // dom id
48874         if (Ext.isString(target)) {
48875             target = Ext.get(target);
48876         }
48877         // dom element
48878         if (target && target.tagName) {
48879             target = Ext.get(target);
48880             targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
48881             me.targets.add(targetObj);
48882             return targetObj;
48883         }
48884         if (Ext.isObject(target)) {
48885             // Element
48886             if (target.dom) {
48887                 targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
48888             }
48889             // Element Composite
48890             else if (target.isComposite) {
48891                 targetObj = Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target);
48892             }
48893             // Draw Sprite
48894             else if (target.isSprite) {
48895                 targetObj = Ext.create('Ext.fx.target.Sprite', target);
48896             }
48897             // Draw Sprite Composite
48898             else if (target.isCompositeSprite) {
48899                 targetObj = Ext.create('Ext.fx.target.CompositeSprite', target);
48900             }
48901             // Component
48902             else if (target.isComponent) {
48903                 targetObj = Ext.create('Ext.fx.target.Component', target);
48904             }
48905             else if (target.isAnimTarget) {
48906                 return target;
48907             }
48908             else {
48909                 return null;
48910             }
48911             me.targets.add(targetObj);
48912             return targetObj;
48913         }
48914         else {
48915             return null;
48916         }
48917     },
48918
48919     /**
48920      * Add an Anim to the manager. This is done automatically when an Anim instance is created.
48921      * @param {Ext.fx.Anim} anim
48922      */
48923     addAnim: function(anim) {
48924         var items = this.items,
48925             task = this.task;
48926         // var me = this,
48927         //     items = me.items,
48928         //     cb = function() {
48929         //         if (items.length) {
48930         //             me.task = true;
48931         //             me.runner();
48932         //             me.requestAnimFrame(cb);
48933         //         }
48934         //         else {
48935         //             me.task = false;
48936         //         }
48937         //     };
48938
48939         items.add(anim);
48940
48941         // Start the timer if not already running
48942         if (!task && items.length) {
48943             task = this.task = {
48944                 run: this.runner,
48945                 interval: this.interval,
48946                 scope: this
48947             };
48948             Ext.TaskManager.start(task);
48949         }
48950
48951         // //Start the timer if not already running
48952         // if (!me.task && items.length) {
48953         //     me.requestAnimFrame(cb);
48954         // }
48955     },
48956
48957     /**
48958      * Remove an Anim from the manager. This is done automatically when an Anim ends.
48959      * @param {Ext.fx.Anim} anim
48960      */
48961     removeAnim: function(anim) {
48962         // this.items.remove(anim);
48963         var items = this.items,
48964             task = this.task;
48965         items.remove(anim);
48966         // Stop the timer if there are no more managed Anims
48967         if (task && !items.length) {
48968             Ext.TaskManager.stop(task);
48969             delete this.task;
48970         }
48971     },
48972
48973     /**
48974      * @private
48975      * Filter function to determine which animations need to be started
48976      */
48977     startingFilter: function(o) {
48978         return o.paused === false && o.running === false && o.iterations > 0;
48979     },
48980
48981     /**
48982      * @private
48983      * Filter function to determine which animations are still running
48984      */
48985     runningFilter: function(o) {
48986         return o.paused === false && o.running === true && o.isAnimator !== true;
48987     },
48988
48989     /**
48990      * @private
48991      * Runner function being called each frame
48992      */
48993     runner: function() {
48994         var me = this,
48995             items = me.items;
48996
48997         me.targetData = {};
48998         me.targetArr = {};
48999
49000         // Single timestamp for all animations this interval
49001         me.timestamp = new Date();
49002
49003         // Start any items not current running
49004         items.filterBy(me.startingFilter).each(me.startAnim, me);
49005
49006         // Build the new attributes to be applied for all targets in this frame
49007         items.filterBy(me.runningFilter).each(me.runAnim, me);
49008
49009         // Apply all the pending changes to their targets
49010         me.applyPendingAttrs();
49011     },
49012
49013     /**
49014      * @private
49015      * Start the individual animation (initialization)
49016      */
49017     startAnim: function(anim) {
49018         anim.start(this.timestamp);
49019     },
49020
49021     /**
49022      * @private
49023      * Run the individual animation for this frame
49024      */
49025     runAnim: function(anim) {
49026         if (!anim) {
49027             return;
49028         }
49029         var me = this,
49030             targetId = anim.target.getId(),
49031             useCSS3 = me.useCSS3 && anim.target.type == 'element',
49032             elapsedTime = me.timestamp - anim.startTime,
49033             target, o;
49034
49035         this.collectTargetData(anim, elapsedTime, useCSS3);
49036
49037         // For CSS3 animation, we need to immediately set the first frame's attributes without any transition
49038         // to get a good initial state, then add the transition properties and set the final attributes.
49039         if (useCSS3) {
49040             // Flush the collected attributes, without transition
49041             anim.target.setAttr(me.targetData[targetId], true);
49042
49043             // Add the end frame data
49044             me.targetData[targetId] = [];
49045             me.collectTargetData(anim, anim.duration, useCSS3);
49046
49047             // Pause the animation so runAnim doesn't keep getting called
49048             anim.paused = true;
49049
49050             target = anim.target.target;
49051             // We only want to attach an event on the last element in a composite
49052             if (anim.target.isComposite) {
49053                 target = anim.target.target.last();
49054             }
49055
49056             // Listen for the transitionend event
49057             o = {};
49058             o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;
49059             o.scope = anim;
49060             o.single = true;
49061             target.on(o);
49062         }
49063         // For JS animation, trigger the lastFrame handler if this is the final frame
49064         else if (elapsedTime >= anim.duration) {
49065             me.applyPendingAttrs(true);
49066             delete me.targetData[targetId];
49067             delete me.targetArr[targetId];
49068             anim.lastFrame();
49069         }
49070     },
49071
49072     /**
49073      * Collect target attributes for the given Anim object at the given timestamp
49074      * @param {Ext.fx.Anim} anim The Anim instance
49075      * @param {Number} timestamp Time after the anim's start time
49076      */
49077     collectTargetData: function(anim, elapsedTime, useCSS3) {
49078         var targetId = anim.target.getId(),
49079             targetData = this.targetData[targetId],
49080             data;
49081         
49082         if (!targetData) {
49083             targetData = this.targetData[targetId] = [];
49084             this.targetArr[targetId] = anim.target;
49085         }
49086
49087         data = {
49088             duration: anim.duration,
49089             easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,
49090             attrs: {}
49091         };
49092         Ext.apply(data.attrs, anim.runAnim(elapsedTime));
49093         targetData.push(data);
49094     },
49095
49096     /**
49097      * @private
49098      * Apply all pending attribute changes to their targets
49099      */
49100     applyPendingAttrs: function(isLastFrame) {
49101         var targetData = this.targetData,
49102             targetArr = this.targetArr,
49103             targetId;
49104         for (targetId in targetData) {
49105             if (targetData.hasOwnProperty(targetId)) {
49106                 targetArr[targetId].setAttr(targetData[targetId], false, isLastFrame);
49107             }
49108         }
49109     }
49110 });
49111
49112 /**
49113  * @class Ext.fx.Animator
49114  * Animation instance
49115
49116 This class is used to run keyframe based animations, which follows the CSS3 based animation structure. 
49117 Keyframe animations differ from typical from/to animations in that they offer the ability to specify values 
49118 at various points throughout the animation.
49119
49120 __Using Keyframes__
49121 The {@link #keyframes} option is the most important part of specifying an animation when using this 
49122 class. A key frame is a point in a particular animation. We represent this as a percentage of the
49123 total animation duration. At each key frame, we can specify the target values at that time. Note that
49124 you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link keyframe}
49125 event that fires after each key frame is reached.
49126
49127 __Example Usage__
49128 In the example below, we modify the values of the element at each fifth throughout the animation.
49129
49130     Ext.create('Ext.fx.Animator', {
49131         target: Ext.getBody().createChild({
49132             style: {
49133                 width: '100px',
49134                 height: '100px',
49135                 'background-color': 'red'
49136             }
49137         }),
49138         duration: 10000, // 10 seconds
49139         keyframes: {
49140             0: {
49141                 opacity: 1,
49142                 backgroundColor: 'FF0000'
49143             },
49144             20: {
49145                 x: 30,
49146                 opacity: 0.5    
49147             },
49148             40: {
49149                 x: 130,
49150                 backgroundColor: '0000FF'    
49151             },
49152             60: {
49153                 y: 80,
49154                 opacity: 0.3    
49155             },
49156             80: {
49157                 width: 200,
49158                 y: 200    
49159             },
49160             100: {
49161                 opacity: 1,
49162                 backgroundColor: '00FF00'
49163             }
49164         }
49165     });
49166
49167  * @markdown
49168  */
49169 Ext.define('Ext.fx.Animator', {
49170
49171     /* Begin Definitions */
49172
49173     mixins: {
49174         observable: 'Ext.util.Observable'
49175     },
49176
49177     requires: ['Ext.fx.Manager'],
49178
49179     /* End Definitions */
49180
49181     isAnimator: true,
49182
49183     /**
49184      * @cfg {Number} duration
49185      * Time in milliseconds for the animation to last. Defaults to 250.
49186      */
49187     duration: 250,
49188
49189     /**
49190      * @cfg {Number} delay
49191      * Time to delay before starting the animation. Defaults to 0.
49192      */
49193     delay: 0,
49194
49195     /* private used to track a delayed starting time */
49196     delayStart: 0,
49197
49198     /**
49199      * @cfg {Boolean} dynamic
49200      * 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.
49201      */
49202     dynamic: false,
49203
49204     /**
49205      * @cfg {String} easing
49206
49207 This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
49208 speed over its duration. 
49209
49210 - backIn
49211 - backOut
49212 - bounceIn
49213 - bounceOut
49214 - ease
49215 - easeIn
49216 - easeOut
49217 - easeInOut
49218 - elasticIn
49219 - elasticOut
49220 - cubic-bezier(x1, y1, x2, y2)
49221
49222 Note that cubic-bezier will create a custom easing curve following the CSS3 transition-timing-function specification `{@link http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag}`. The four values specify points P1 and P2 of the curve
49223 as (x1, y1, x2, y2). All values must be in the range [0, 1] or the definition is invalid.
49224
49225      * @markdown
49226      */
49227     easing: 'ease',
49228
49229     /**
49230      * Flag to determine if the animation has started
49231      * @property running
49232      * @type boolean
49233      */
49234     running: false,
49235
49236     /**
49237      * Flag to determine if the animation is paused. Only set this to true if you need to
49238      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
49239      * @property paused
49240      * @type boolean
49241      */
49242     paused: false,
49243
49244     /**
49245      * @private
49246      */
49247     damper: 1,
49248
49249     /**
49250      * @cfg {Number} iterations
49251      * Number of times to execute the animation. Defaults to 1.
49252      */
49253     iterations: 1,
49254
49255     /**
49256      * Current iteration the animation is running.
49257      * @property currentIteration
49258      * @type int
49259      */
49260     currentIteration: 0,
49261
49262     /**
49263      * Current keyframe step of the animation.
49264      * @property keyframeStep
49265      * @type Number
49266      */
49267     keyframeStep: 0,
49268
49269     /**
49270      * @private
49271      */
49272     animKeyFramesRE: /^(from|to|\d+%?)$/,
49273
49274     /**
49275      * @cfg {Ext.fx.target} target
49276      * The Ext.fx.target to apply the animation to.  If not specified during initialization, this can be passed to the applyAnimator
49277      * method to apply the same animation to many targets.
49278      */
49279
49280      /**
49281       * @cfg {Object} keyframes
49282       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
49283       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
49284       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
49285       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
49286       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
49287  <pre><code>
49288 keyframes : {
49289     '0%': {
49290         left: 100
49291     },
49292     '40%': {
49293         left: 150
49294     },
49295     '60%': {
49296         left: 75
49297     },
49298     '100%': {
49299         left: 100
49300     }
49301 }
49302  </code></pre>
49303       */
49304     constructor: function(config) {
49305         var me = this;
49306         config = Ext.apply(me, config || {});
49307         me.config = config;
49308         me.id = Ext.id(null, 'ext-animator-');
49309         me.addEvents(
49310             /**
49311              * @event beforeanimate
49312              * Fires before the animation starts. A handler can return false to cancel the animation.
49313              * @param {Ext.fx.Animator} this
49314              */
49315             'beforeanimate',
49316             /**
49317               * @event keyframe
49318               * Fires at each keyframe.
49319               * @param {Ext.fx.Animator} this
49320               * @param {Number} keyframe step number
49321               */
49322             'keyframe',
49323             /**
49324              * @event afteranimate
49325              * Fires when the animation is complete.
49326              * @param {Ext.fx.Animator} this
49327              * @param {Date} startTime
49328              */
49329             'afteranimate'
49330         );
49331         me.mixins.observable.constructor.call(me, config);
49332         me.timeline = [];
49333         me.createTimeline(me.keyframes);
49334         if (me.target) {
49335             me.applyAnimator(me.target);
49336             Ext.fx.Manager.addAnim(me);
49337         }
49338     },
49339
49340     /**
49341      * @private
49342      */
49343     sorter: function (a, b) {
49344         return a.pct - b.pct;
49345     },
49346
49347     /**
49348      * @private
49349      * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
49350      * or applying the 'to' configuration to all keyframes.  Also calculates the proper animation duration per keyframe.
49351      */
49352     createTimeline: function(keyframes) {
49353         var me = this,
49354             attrs = [],
49355             to = me.to || {},
49356             duration = me.duration,
49357             prevMs, ms, i, ln, pct, anim, nextAnim, attr;
49358
49359         for (pct in keyframes) {
49360             if (keyframes.hasOwnProperty(pct) && me.animKeyFramesRE.test(pct)) {
49361                 attr = {attrs: Ext.apply(keyframes[pct], to)};
49362                 // CSS3 spec allow for from/to to be specified.
49363                 if (pct == "from") {
49364                     pct = 0;
49365                 }
49366                 else if (pct == "to") {
49367                     pct = 100;
49368                 }
49369                 // convert % values into integers
49370                 attr.pct = parseInt(pct, 10);
49371                 attrs.push(attr);
49372             }
49373         }
49374         // Sort by pct property
49375         Ext.Array.sort(attrs, me.sorter);
49376         // Only an end
49377         //if (attrs[0].pct) {
49378         //    attrs.unshift({pct: 0, attrs: element.attrs});
49379         //}
49380
49381         ln = attrs.length;
49382         for (i = 0; i < ln; i++) {
49383             prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
49384             ms = duration * (attrs[i].pct / 100);
49385             me.timeline.push({
49386                 duration: ms - prevMs,
49387                 attrs: attrs[i].attrs
49388             });
49389         }
49390     },
49391
49392     /**
49393      * Applies animation to the Ext.fx.target
49394      * @private
49395      * @param target
49396      * @type string/object
49397      */
49398     applyAnimator: function(target) {
49399         var me = this,
49400             anims = [],
49401             timeline = me.timeline,
49402             reverse = me.reverse,
49403             ln = timeline.length,
49404             anim, easing, damper, initial, attrs, lastAttrs, i;
49405
49406         if (me.fireEvent('beforeanimate', me) !== false) {
49407             for (i = 0; i < ln; i++) {
49408                 anim = timeline[i];
49409                 attrs = anim.attrs;
49410                 easing = attrs.easing || me.easing;
49411                 damper = attrs.damper || me.damper;
49412                 delete attrs.easing;
49413                 delete attrs.damper;
49414                 anim = Ext.create('Ext.fx.Anim', {
49415                     target: target,
49416                     easing: easing,
49417                     damper: damper,
49418                     duration: anim.duration,
49419                     paused: true,
49420                     to: attrs
49421                 });
49422                 anims.push(anim);
49423             }
49424             me.animations = anims;
49425             me.target = anim.target;
49426             for (i = 0; i < ln - 1; i++) {
49427                 anim = anims[i];
49428                 anim.nextAnim = anims[i + 1];
49429                 anim.on('afteranimate', function() {
49430                     this.nextAnim.paused = false;
49431                 });
49432                 anim.on('afteranimate', function() {
49433                     this.fireEvent('keyframe', this, ++this.keyframeStep);
49434                 }, me);
49435             }
49436             anims[ln - 1].on('afteranimate', function() {
49437                 this.lastFrame();
49438             }, me);
49439         }
49440     },
49441
49442     /*
49443      * @private
49444      * Fires beforeanimate and sets the running flag.
49445      */
49446     start: function(startTime) {
49447         var me = this,
49448             delay = me.delay,
49449             delayStart = me.delayStart,
49450             delayDelta;
49451         if (delay) {
49452             if (!delayStart) {
49453                 me.delayStart = startTime;
49454                 return;
49455             }
49456             else {
49457                 delayDelta = startTime - delayStart;
49458                 if (delayDelta < delay) {
49459                     return;
49460                 }
49461                 else {
49462                     // Compensate for frame delay;
49463                     startTime = new Date(delayStart.getTime() + delay);
49464                 }
49465             }
49466         }
49467         if (me.fireEvent('beforeanimate', me) !== false) {
49468             me.startTime = startTime;
49469             me.running = true;
49470             me.animations[me.keyframeStep].paused = false;
49471         }
49472     },
49473
49474     /*
49475      * @private
49476      * Perform lastFrame cleanup and handle iterations
49477      * @returns a hash of the new attributes.
49478      */
49479     lastFrame: function() {
49480         var me = this,
49481             iter = me.iterations,
49482             iterCount = me.currentIteration;
49483
49484         iterCount++;
49485         if (iterCount < iter) {
49486             me.startTime = new Date();
49487             me.currentIteration = iterCount;
49488             me.keyframeStep = 0;
49489             me.applyAnimator(me.target);
49490             me.animations[me.keyframeStep].paused = false;
49491         }
49492         else {
49493             me.currentIteration = 0;
49494             me.end();
49495         }
49496     },
49497
49498     /*
49499      * Fire afteranimate event and end the animation. Usually called automatically when the
49500      * animation reaches its final frame, but can also be called manually to pre-emptively
49501      * stop and destroy the running animation.
49502      */
49503     end: function() {
49504         var me = this;
49505         me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
49506     }
49507 });
49508 /**
49509  * @class Ext.fx.Easing
49510  * 
49511 This class contains a series of function definitions used to modify values during an animation.
49512 They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
49513 speed over its duration. The following options are available: 
49514
49515 - linear The default easing type
49516 - backIn
49517 - backOut
49518 - bounceIn
49519 - bounceOut
49520 - ease
49521 - easeIn
49522 - easeOut
49523 - easeInOut
49524 - elasticIn
49525 - elasticOut
49526 - cubic-bezier(x1, y1, x2, y2)
49527
49528 Note that cubic-bezier will create a custom easing curve following the CSS3 transition-timing-function specification `{@link http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag}`. The four values specify points P1 and P2 of the curve
49529 as (x1, y1, x2, y2). All values must be in the range [0, 1] or the definition is invalid.
49530  * @markdown
49531  * @singleton
49532  */
49533 Ext.ns('Ext.fx');
49534
49535 Ext.require('Ext.fx.CubicBezier', function() {
49536     var math = Math,
49537         pi = math.PI,
49538         pow = math.pow,
49539         sin = math.sin,
49540         sqrt = math.sqrt,
49541         abs = math.abs,
49542         backInSeed = 1.70158;
49543     Ext.fx.Easing = {
49544         // ease: Ext.fx.CubicBezier.cubicBezier(0.25, 0.1, 0.25, 1),
49545         // linear: Ext.fx.CubicBezier.cubicBezier(0, 0, 1, 1),
49546         // 'ease-in': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
49547         // 'ease-out': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
49548         // 'ease-in-out': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1),
49549         // 'easeIn': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
49550         // 'easeOut': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
49551         // 'easeInOut': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1)
49552     };
49553
49554     Ext.apply(Ext.fx.Easing, {
49555         linear: function(n) {
49556             return n;
49557         },
49558         ease: function(n) {
49559             var q = 0.07813 - n / 2,
49560                 alpha = -0.25,
49561                 Q = sqrt(0.0066 + q * q),
49562                 x = Q - q,
49563                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
49564                 y = -Q - q,
49565                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
49566                 t = X + Y + 0.25;
49567             return pow(1 - t, 2) * 3 * t * 0.1 + (1 - t) * 3 * t * t + t * t * t;
49568         },
49569         easeIn: function (n) {
49570             return pow(n, 1.7);
49571         },
49572         easeOut: function (n) {
49573             return pow(n, 0.48);
49574         },
49575         easeInOut: function(n) {
49576             var q = 0.48 - n / 1.04,
49577                 Q = sqrt(0.1734 + q * q),
49578                 x = Q - q,
49579                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
49580                 y = -Q - q,
49581                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
49582                 t = X + Y + 0.5;
49583             return (1 - t) * 3 * t * t + t * t * t;
49584         },
49585         backIn: function (n) {
49586             return n * n * ((backInSeed + 1) * n - backInSeed);
49587         },
49588         backOut: function (n) {
49589             n = n - 1;
49590             return n * n * ((backInSeed + 1) * n + backInSeed) + 1;
49591         },
49592         elasticIn: function (n) {
49593             if (n === 0 || n === 1) {
49594                 return n;
49595             }
49596             var p = 0.3,
49597                 s = p / 4;
49598             return pow(2, -10 * n) * sin((n - s) * (2 * pi) / p) + 1;
49599         },
49600         elasticOut: function (n) {
49601             return 1 - Ext.fx.Easing.elasticIn(1 - n);
49602         },
49603         bounceIn: function (n) {
49604             return 1 - Ext.fx.Easing.bounceOut(1 - n);
49605         },
49606         bounceOut: function (n) {
49607             var s = 7.5625,
49608                 p = 2.75,
49609                 l;
49610             if (n < (1 / p)) {
49611                 l = s * n * n;
49612             } else {
49613                 if (n < (2 / p)) {
49614                     n -= (1.5 / p);
49615                     l = s * n * n + 0.75;
49616                 } else {
49617                     if (n < (2.5 / p)) {
49618                         n -= (2.25 / p);
49619                         l = s * n * n + 0.9375;
49620                     } else {
49621                         n -= (2.625 / p);
49622                         l = s * n * n + 0.984375;
49623                     }
49624                 }
49625             }
49626             return l;
49627         }
49628     });
49629     Ext.apply(Ext.fx.Easing, {
49630         'back-in': Ext.fx.Easing.backIn,
49631         'back-out': Ext.fx.Easing.backOut,
49632         'ease-in': Ext.fx.Easing.easeIn,
49633         'ease-out': Ext.fx.Easing.easeOut,
49634         'elastic-in': Ext.fx.Easing.elasticIn,
49635         'elastic-out': Ext.fx.Easing.elasticIn,
49636         'bounce-in': Ext.fx.Easing.bounceIn,
49637         'bounce-out': Ext.fx.Easing.bounceOut,
49638         'ease-in-out': Ext.fx.Easing.easeInOut
49639     });
49640 });
49641 /*
49642  * @class Ext.draw.Draw
49643  * Base Drawing class.  Provides base drawing functions.
49644  */
49645
49646 Ext.define('Ext.draw.Draw', {
49647     /* Begin Definitions */
49648
49649     singleton: true,
49650
49651     requires: ['Ext.draw.Color'],
49652
49653     /* End Definitions */
49654
49655     pathToStringRE: /,?([achlmqrstvxz]),?/gi,
49656     pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
49657     pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
49658     stopsRE: /^(\d+%?)$/,
49659     radian: Math.PI / 180,
49660
49661     availableAnimAttrs: {
49662         along: "along",
49663         blur: null,
49664         "clip-rect": "csv",
49665         cx: null,
49666         cy: null,
49667         fill: "color",
49668         "fill-opacity": null,
49669         "font-size": null,
49670         height: null,
49671         opacity: null,
49672         path: "path",
49673         r: null,
49674         rotation: "csv",
49675         rx: null,
49676         ry: null,
49677         scale: "csv",
49678         stroke: "color",
49679         "stroke-opacity": null,
49680         "stroke-width": null,
49681         translation: "csv",
49682         width: null,
49683         x: null,
49684         y: null
49685     },
49686
49687     is: function(o, type) {
49688         type = String(type).toLowerCase();
49689         return (type == "object" && o === Object(o)) ||
49690             (type == "undefined" && typeof o == type) ||
49691             (type == "null" && o === null) ||
49692             (type == "array" && Array.isArray && Array.isArray(o)) ||
49693             (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
49694     },
49695
49696     ellipsePath: function(sprite) {
49697         var attr = sprite.attr;
49698         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);
49699     },
49700
49701     rectPath: function(sprite) {
49702         var attr = sprite.attr;
49703         if (attr.radius) {
49704             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);
49705         }
49706         else {
49707             return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
49708         }
49709     },
49710
49711     // To be deprecated, converts itself (an arrayPath) to a proper SVG path string
49712     path2string: function () {
49713         return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
49714     },
49715
49716     // Convert the passed arrayPath to a proper SVG path string (d attribute)
49717     pathToString: function(arrayPath) {
49718         return arrayPath.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
49719     },
49720
49721     parsePathString: function (pathString) {
49722         if (!pathString) {
49723             return null;
49724         }
49725         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
49726             data = [],
49727             me = this;
49728         if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
49729             data = me.pathClone(pathString);
49730         }
49731         if (!data.length) {
49732             String(pathString).replace(me.pathCommandRE, function (a, b, c) {
49733                 var params = [],
49734                     name = b.toLowerCase();
49735                 c.replace(me.pathValuesRE, function (a, b) {
49736                     b && params.push(+b);
49737                 });
49738                 if (name == "m" && params.length > 2) {
49739                     data.push([b].concat(params.splice(0, 2)));
49740                     name = "l";
49741                     b = (b == "m") ? "l" : "L";
49742                 }
49743                 while (params.length >= paramCounts[name]) {
49744                     data.push([b].concat(params.splice(0, paramCounts[name])));
49745                     if (!paramCounts[name]) {
49746                         break;
49747                     }
49748                 }
49749             });
49750         }
49751         data.toString = me.path2string;
49752         return data;
49753     },
49754
49755     mapPath: function (path, matrix) {
49756         if (!matrix) {
49757             return path;
49758         }
49759         var x, y, i, ii, j, jj, pathi;
49760         path = this.path2curve(path);
49761         for (i = 0, ii = path.length; i < ii; i++) {
49762             pathi = path[i];
49763             for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
49764                 x = matrix.x(pathi[j], pathi[j + 1]);
49765                 y = matrix.y(pathi[j], pathi[j + 1]);
49766                 pathi[j] = x;
49767                 pathi[j + 1] = y;
49768             }
49769         }
49770         return path;
49771     },
49772
49773     pathClone: function(pathArray) {
49774         var res = [],
49775             j, jj, i, ii;
49776         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
49777             pathArray = this.parsePathString(pathArray);
49778         }
49779         for (i = 0, ii = pathArray.length; i < ii; i++) {
49780             res[i] = [];
49781             for (j = 0, jj = pathArray[i].length; j < jj; j++) {
49782                 res[i][j] = pathArray[i][j];
49783             }
49784         }
49785         res.toString = this.path2string;
49786         return res;
49787     },
49788
49789     pathToAbsolute: function (pathArray) {
49790         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
49791             pathArray = this.parsePathString(pathArray);
49792         }
49793         var res = [],
49794             x = 0,
49795             y = 0,
49796             mx = 0,
49797             my = 0,
49798             i = 0,
49799             ln = pathArray.length,
49800             r, pathSegment, j, ln2;
49801         // MoveTo initial x/y position
49802         if (ln && pathArray[0][0] == "M") {
49803             x = +pathArray[0][1];
49804             y = +pathArray[0][2];
49805             mx = x;
49806             my = y;
49807             i++;
49808             res[0] = ["M", x, y];
49809         }
49810         for (; i < ln; i++) {
49811             r = res[i] = [];
49812             pathSegment = pathArray[i];
49813             if (pathSegment[0] != pathSegment[0].toUpperCase()) {
49814                 r[0] = pathSegment[0].toUpperCase();
49815                 switch (r[0]) {
49816                     // Elliptical Arc
49817                     case "A":
49818                         r[1] = pathSegment[1];
49819                         r[2] = pathSegment[2];
49820                         r[3] = pathSegment[3];
49821                         r[4] = pathSegment[4];
49822                         r[5] = pathSegment[5];
49823                         r[6] = +(pathSegment[6] + x);
49824                         r[7] = +(pathSegment[7] + y);
49825                         break;
49826                     // Vertical LineTo
49827                     case "V":
49828                         r[1] = +pathSegment[1] + y;
49829                         break;
49830                     // Horizontal LineTo
49831                     case "H":
49832                         r[1] = +pathSegment[1] + x;
49833                         break;
49834                     case "M":
49835                     // MoveTo
49836                         mx = +pathSegment[1] + x;
49837                         my = +pathSegment[2] + y;
49838                     default:
49839                         j = 1;
49840                         ln2 = pathSegment.length;
49841                         for (; j < ln2; j++) {
49842                             r[j] = +pathSegment[j] + ((j % 2) ? x : y);
49843                         }
49844                 }
49845             }
49846             else {
49847                 j = 0;
49848                 ln2 = pathSegment.length;
49849                 for (; j < ln2; j++) {
49850                     res[i][j] = pathSegment[j];
49851                 }
49852             }
49853             switch (r[0]) {
49854                 // ClosePath
49855                 case "Z":
49856                     x = mx;
49857                     y = my;
49858                     break;
49859                 // Horizontal LineTo
49860                 case "H":
49861                     x = r[1];
49862                     break;
49863                 // Vertical LineTo
49864                 case "V":
49865                     y = r[1];
49866                     break;
49867                 // MoveTo
49868                 case "M":
49869                     pathSegment = res[i];
49870                     ln2 = pathSegment.length;
49871                     mx = pathSegment[ln2 - 2];
49872                     my = pathSegment[ln2 - 1];
49873                 default:
49874                     pathSegment = res[i];
49875                     ln2 = pathSegment.length;
49876                     x = pathSegment[ln2 - 2];
49877                     y = pathSegment[ln2 - 1];
49878             }
49879         }
49880         res.toString = this.path2string;
49881         return res;
49882     },
49883
49884     // TO BE DEPRECATED
49885     pathToRelative: function (pathArray) {
49886         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
49887             pathArray = this.parsePathString(pathArray);
49888         }
49889         var res = [],
49890             x = 0,
49891             y = 0,
49892             mx = 0,
49893             my = 0,
49894             start = 0;
49895         if (pathArray[0][0] == "M") {
49896             x = pathArray[0][1];
49897             y = pathArray[0][2];
49898             mx = x;
49899             my = y;
49900             start++;
49901             res.push(["M", x, y]);
49902         }
49903         for (var i = start, ii = pathArray.length; i < ii; i++) {
49904             var r = res[i] = [],
49905                 pa = pathArray[i];
49906             if (pa[0] != pa[0].toLowerCase()) {
49907                 r[0] = pa[0].toLowerCase();
49908                 switch (r[0]) {
49909                     case "a":
49910                         r[1] = pa[1];
49911                         r[2] = pa[2];
49912                         r[3] = pa[3];
49913                         r[4] = pa[4];
49914                         r[5] = pa[5];
49915                         r[6] = +(pa[6] - x).toFixed(3);
49916                         r[7] = +(pa[7] - y).toFixed(3);
49917                         break;
49918                     case "v":
49919                         r[1] = +(pa[1] - y).toFixed(3);
49920                         break;
49921                     case "m":
49922                         mx = pa[1];
49923                         my = pa[2];
49924                     default:
49925                         for (var j = 1, jj = pa.length; j < jj; j++) {
49926                             r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
49927                         }
49928                 }
49929             } else {
49930                 r = res[i] = [];
49931                 if (pa[0] == "m") {
49932                     mx = pa[1] + x;
49933                     my = pa[2] + y;
49934                 }
49935                 for (var k = 0, kk = pa.length; k < kk; k++) {
49936                     res[i][k] = pa[k];
49937                 }
49938             }
49939             var len = res[i].length;
49940             switch (res[i][0]) {
49941                 case "z":
49942                     x = mx;
49943                     y = my;
49944                     break;
49945                 case "h":
49946                     x += +res[i][len - 1];
49947                     break;
49948                 case "v":
49949                     y += +res[i][len - 1];
49950                     break;
49951                 default:
49952                     x += +res[i][len - 2];
49953                     y += +res[i][len - 1];
49954             }
49955         }
49956         res.toString = this.path2string;
49957         return res;
49958     },
49959
49960     // Returns a path converted to a set of curveto commands
49961     path2curve: function (path) {
49962         var me = this,
49963             points = me.pathToAbsolute(path),
49964             ln = points.length,
49965             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
49966             i, seg, segLn, point;
49967             
49968         for (i = 0; i < ln; i++) {
49969             points[i] = me.command2curve(points[i], attrs);
49970             if (points[i].length > 7) {
49971                     points[i].shift();
49972                     point = points[i];
49973                     while (point.length) {
49974                         points.splice(i++, 0, ["C"].concat(point.splice(0, 6)));
49975                     }
49976                     points.splice(i, 1);
49977                     ln = points.length;
49978                 }
49979             seg = points[i];
49980             segLn = seg.length;
49981             attrs.x = seg[segLn - 2];
49982             attrs.y = seg[segLn - 1];
49983             attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
49984             attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
49985         }
49986         return points;
49987     },
49988     
49989     interpolatePaths: function (path, path2) {
49990         var me = this,
49991             p = me.pathToAbsolute(path),
49992             p2 = me.pathToAbsolute(path2),
49993             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
49994             attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
49995             fixArc = function (pp, i) {
49996                 if (pp[i].length > 7) {
49997                     pp[i].shift();
49998                     var pi = pp[i];
49999                     while (pi.length) {
50000                         pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
50001                     }
50002                     pp.splice(i, 1);
50003                     ii = Math.max(p.length, p2.length || 0);
50004                 }
50005             },
50006             fixM = function (path1, path2, a1, a2, i) {
50007                 if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
50008                     path2.splice(i, 0, ["M", a2.x, a2.y]);
50009                     a1.bx = 0;
50010                     a1.by = 0;
50011                     a1.x = path1[i][1];
50012                     a1.y = path1[i][2];
50013                     ii = Math.max(p.length, p2.length || 0);
50014                 }
50015             };
50016         for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
50017             p[i] = me.command2curve(p[i], attrs);
50018             fixArc(p, i);
50019             (p2[i] = me.command2curve(p2[i], attrs2));
50020             fixArc(p2, i);
50021             fixM(p, p2, attrs, attrs2, i);
50022             fixM(p2, p, attrs2, attrs, i);
50023             var seg = p[i],
50024                 seg2 = p2[i],
50025                 seglen = seg.length,
50026                 seg2len = seg2.length;
50027             attrs.x = seg[seglen - 2];
50028             attrs.y = seg[seglen - 1];
50029             attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
50030             attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
50031             attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
50032             attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
50033             attrs2.x = seg2[seg2len - 2];
50034             attrs2.y = seg2[seg2len - 1];
50035         }
50036         return [p, p2];
50037     },
50038     
50039     //Returns any path command as a curveto command based on the attrs passed
50040     command2curve: function (pathCommand, d) {
50041         var me = this;
50042         if (!pathCommand) {
50043             return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
50044         }
50045         if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
50046             d.qx = d.qy = null;
50047         }
50048         switch (pathCommand[0]) {
50049             case "M":
50050                 d.X = pathCommand[1];
50051                 d.Y = pathCommand[2];
50052                 break;
50053             case "A":
50054                 pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
50055                 break;
50056             case "S":
50057                 pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
50058                 break;
50059             case "T":
50060                 d.qx = d.x + (d.x - (d.qx || d.x));
50061                 d.qy = d.y + (d.y - (d.qy || d.y));
50062                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
50063                 break;
50064             case "Q":
50065                 d.qx = pathCommand[1];
50066                 d.qy = pathCommand[2];
50067                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
50068                 break;
50069             case "L":
50070                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
50071                 break;
50072             case "H":
50073                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
50074                 break;
50075             case "V":
50076                 pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
50077                 break;
50078             case "Z":
50079                 pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
50080                 break;
50081         }
50082         return pathCommand;
50083     },
50084
50085     quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
50086         var _13 = 1 / 3,
50087             _23 = 2 / 3;
50088         return [
50089                 _13 * x1 + _23 * ax,
50090                 _13 * y1 + _23 * ay,
50091                 _13 * x2 + _23 * ax,
50092                 _13 * y2 + _23 * ay,
50093                 x2,
50094                 y2
50095             ];
50096     },
50097     
50098     rotate: function (x, y, rad) {
50099         var cos = Math.cos(rad),
50100             sin = Math.sin(rad),
50101             X = x * cos - y * sin,
50102             Y = x * sin + y * cos;
50103         return {x: X, y: Y};
50104     },
50105
50106     arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
50107         // for more information of where this Math came from visit:
50108         // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
50109         var me = this,
50110             PI = Math.PI,
50111             radian = me.radian,
50112             _120 = PI * 120 / 180,
50113             rad = radian * (+angle || 0),
50114             res = [],
50115             math = Math,
50116             mcos = math.cos,
50117             msin = math.sin,
50118             msqrt = math.sqrt,
50119             mabs = math.abs,
50120             masin = math.asin,
50121             xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
50122             t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
50123         if (!recursive) {
50124             xy = me.rotate(x1, y1, -rad);
50125             x1 = xy.x;
50126             y1 = xy.y;
50127             xy = me.rotate(x2, y2, -rad);
50128             x2 = xy.x;
50129             y2 = xy.y;
50130             cos = mcos(radian * angle);
50131             sin = msin(radian * angle);
50132             x = (x1 - x2) / 2;
50133             y = (y1 - y2) / 2;
50134             h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
50135             if (h > 1) {
50136                 h = msqrt(h);
50137                 rx = h * rx;
50138                 ry = h * ry;
50139             }
50140             rx2 = rx * rx;
50141             ry2 = ry * ry;
50142             k = (large_arc_flag == sweep_flag ? -1 : 1) *
50143                     msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
50144             cx = k * rx * y / ry + (x1 + x2) / 2;
50145             cy = k * -ry * x / rx + (y1 + y2) / 2;
50146             f1 = masin(((y1 - cy) / ry).toFixed(7));
50147             f2 = masin(((y2 - cy) / ry).toFixed(7));
50148
50149             f1 = x1 < cx ? PI - f1 : f1;
50150             f2 = x2 < cx ? PI - f2 : f2;
50151             if (f1 < 0) {
50152                 f1 = PI * 2 + f1;
50153             }
50154             if (f2 < 0) {
50155                 f2 = PI * 2 + f2;
50156             }
50157             if (sweep_flag && f1 > f2) {
50158                 f1 = f1 - PI * 2;
50159             }
50160             if (!sweep_flag && f2 > f1) {
50161                 f2 = f2 - PI * 2;
50162             }
50163         }
50164         else {
50165             f1 = recursive[0];
50166             f2 = recursive[1];
50167             cx = recursive[2];
50168             cy = recursive[3];
50169         }
50170         df = f2 - f1;
50171         if (mabs(df) > _120) {
50172             f2old = f2;
50173             x2old = x2;
50174             y2old = y2;
50175             f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
50176             x2 = cx + rx * mcos(f2);
50177             y2 = cy + ry * msin(f2);
50178             res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
50179         }
50180         df = f2 - f1;
50181         c1 = mcos(f1);
50182         s1 = msin(f1);
50183         c2 = mcos(f2);
50184         s2 = msin(f2);
50185         t = math.tan(df / 4);
50186         hx = 4 / 3 * rx * t;
50187         hy = 4 / 3 * ry * t;
50188         m1 = [x1, y1];
50189         m2 = [x1 + hx * s1, y1 - hy * c1];
50190         m3 = [x2 + hx * s2, y2 - hy * c2];
50191         m4 = [x2, y2];
50192         m2[0] = 2 * m1[0] - m2[0];
50193         m2[1] = 2 * m1[1] - m2[1];
50194         if (recursive) {
50195             return [m2, m3, m4].concat(res);
50196         }
50197         else {
50198             res = [m2, m3, m4].concat(res).join().split(",");
50199             newres = [];
50200             ln = res.length;
50201             for (i = 0;  i < ln; i++) {
50202                 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
50203             }
50204             return newres;
50205         }
50206     },
50207
50208     // TO BE DEPRECATED
50209     rotateAndTranslatePath: function (sprite) {
50210         var alpha = sprite.rotation.degrees,
50211             cx = sprite.rotation.x,
50212             cy = sprite.rotation.y,
50213             dx = sprite.translation.x,
50214             dy = sprite.translation.y,
50215             path,
50216             i,
50217             p,
50218             xy,
50219             j,
50220             res = [];
50221         if (!alpha && !dx && !dy) {
50222             return this.pathToAbsolute(sprite.attr.path);
50223         }
50224         dx = dx || 0;
50225         dy = dy || 0;
50226         path = this.pathToAbsolute(sprite.attr.path);
50227         for (i = path.length; i--;) {
50228             p = res[i] = path[i].slice();
50229             if (p[0] == "A") {
50230                 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
50231                 p[6] = xy.x + dx;
50232                 p[7] = xy.y + dy;
50233             } else {
50234                 j = 1;
50235                 while (p[j + 1] != null) {
50236                     xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
50237                     p[j] = xy.x + dx;
50238                     p[j + 1] = xy.y + dy;
50239                     j += 2;
50240                 }
50241             }
50242         }
50243         return res;
50244     },
50245
50246     // TO BE DEPRECATED
50247     rotatePoint: function (x, y, alpha, cx, cy) {
50248         if (!alpha) {
50249             return {
50250                 x: x,
50251                 y: y
50252             };
50253         }
50254         cx = cx || 0;
50255         cy = cy || 0;
50256         x = x - cx;
50257         y = y - cy;
50258         alpha = alpha * this.radian;
50259         var cos = Math.cos(alpha),
50260             sin = Math.sin(alpha);
50261         return {
50262             x: x * cos - y * sin + cx,
50263             y: x * sin + y * cos + cy
50264         };
50265     },
50266
50267     pathDimensions: function (path) {
50268         if (!path || !(path + "")) {
50269             return {x: 0, y: 0, width: 0, height: 0};
50270         }
50271         path = this.path2curve(path);
50272         var x = 0, 
50273             y = 0,
50274             X = [],
50275             Y = [],
50276             i = 0,
50277             ln = path.length,
50278             p, xmin, ymin, dim;
50279         for (; i < ln; i++) {
50280             p = path[i];
50281             if (p[0] == "M") {
50282                 x = p[1];
50283                 y = p[2];
50284                 X.push(x);
50285                 Y.push(y);
50286             }
50287             else {
50288                 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
50289                 X = X.concat(dim.min.x, dim.max.x);
50290                 Y = Y.concat(dim.min.y, dim.max.y);
50291                 x = p[5];
50292                 y = p[6];
50293             }
50294         }
50295         xmin = Math.min.apply(0, X);
50296         ymin = Math.min.apply(0, Y);
50297         return {
50298             x: xmin,
50299             y: ymin,
50300             path: path,
50301             width: Math.max.apply(0, X) - xmin,
50302             height: Math.max.apply(0, Y) - ymin
50303         };
50304     },
50305
50306     intersectInside: function(path, cp1, cp2) {
50307         return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) > (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
50308     },
50309
50310     intersectIntersection: function(s, e, cp1, cp2) {
50311         var p = [],
50312             dcx = cp1[0] - cp2[0],
50313             dcy = cp1[1] - cp2[1],
50314             dpx = s[0] - e[0],
50315             dpy = s[1] - e[1],
50316             n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
50317             n2 = s[0] * e[1] - s[1] * e[0],
50318             n3 = 1 / (dcx * dpy - dcy * dpx);
50319
50320         p[0] = (n1 * dpx - n2 * dcx) * n3;
50321         p[1] = (n1 * dpy - n2 * dcy) * n3;
50322         return p;
50323     },
50324
50325     intersect: function(subjectPolygon, clipPolygon) {
50326         var me = this,
50327             i = 0,
50328             ln = clipPolygon.length,
50329             cp1 = clipPolygon[ln - 1],
50330             outputList = subjectPolygon,
50331             cp2, s, e, point, ln2, inputList, j;
50332         for (; i < ln; ++i) {
50333             cp2 = clipPolygon[i];
50334             inputList = outputList;
50335             outputList = [];
50336             s = inputList[inputList.length - 1];
50337             j = 0;
50338             ln2 = inputList.length;
50339             for (; j < ln2; j++) {
50340                 e = inputList[j];
50341                 if (me.intersectInside(e, cp1, cp2)) {
50342                     if (!me.intersectInside(s, cp1, cp2)) {
50343                         outputList.push(me.intersectIntersection(s, e, cp1, cp2));
50344                     }
50345                     outputList.push(e);
50346                 }
50347                 else if (me.intersectInside(s, cp1, cp2)) {
50348                     outputList.push(me.intersectIntersection(s, e, cp1, cp2));
50349                 }
50350                 s = e;
50351             }
50352             cp1 = cp2;
50353         }
50354         return outputList;
50355     },
50356
50357     curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
50358         var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
50359             b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
50360             c = p1x - c1x,
50361             t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
50362             t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
50363             y = [p1y, p2y],
50364             x = [p1x, p2x],
50365             dot;
50366         if (Math.abs(t1) > 1e12) {
50367             t1 = 0.5;
50368         }
50369         if (Math.abs(t2) > 1e12) {
50370             t2 = 0.5;
50371         }
50372         if (t1 > 0 && t1 < 1) {
50373             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
50374             x.push(dot.x);
50375             y.push(dot.y);
50376         }
50377         if (t2 > 0 && t2 < 1) {
50378             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
50379             x.push(dot.x);
50380             y.push(dot.y);
50381         }
50382         a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
50383         b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
50384         c = p1y - c1y;
50385         t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
50386         t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
50387         if (Math.abs(t1) > 1e12) {
50388             t1 = 0.5;
50389         }
50390         if (Math.abs(t2) > 1e12) {
50391             t2 = 0.5;
50392         }
50393         if (t1 > 0 && t1 < 1) {
50394             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
50395             x.push(dot.x);
50396             y.push(dot.y);
50397         }
50398         if (t2 > 0 && t2 < 1) {
50399             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
50400             x.push(dot.x);
50401             y.push(dot.y);
50402         }
50403         return {
50404             min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
50405             max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
50406         };
50407     },
50408
50409     getAnchors: function (p1x, p1y, p2x, p2y, p3x, p3y, value) {
50410         value = value || 4;
50411         var l = Math.min(Math.sqrt(Math.pow(p1x - p2x, 2) + Math.pow(p1y - p2y, 2)) / value, Math.sqrt(Math.pow(p3x - p2x, 2) + Math.pow(p3y - p2y, 2)) / value),
50412             a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
50413             b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y)),
50414             pi = Math.PI;
50415         a = p1y < p2y ? pi - a : a;
50416         b = p3y < p2y ? pi - b : b;
50417         var alpha = pi / 2 - ((a + b) % (pi * 2)) / 2;
50418         alpha > pi / 2 && (alpha -= pi);
50419         var dx1 = l * Math.sin(alpha + a),
50420             dy1 = l * Math.cos(alpha + a),
50421             dx2 = l * Math.sin(alpha + b),
50422             dy2 = l * Math.cos(alpha + b),
50423             out = {
50424                 x1: p2x - dx1,
50425                 y1: p2y + dy1,
50426                 x2: p2x + dx2,
50427                 y2: p2y + dy2
50428             };
50429         return out;
50430     },
50431
50432     /* Smoothing function for a path.  Converts a path into cubic beziers.  Value defines the divider of the distance between points.
50433      * Defaults to a value of 4.
50434      */
50435     smooth: function (originalPath, value) {
50436         var path = this.path2curve(originalPath),
50437             newp = [path[0]],
50438             x = path[0][1],
50439             y = path[0][2],
50440             j,
50441             points,
50442             i = 1,
50443             ii = path.length,
50444             beg = 1,
50445             mx = x,
50446             my = y,
50447             cx = 0,
50448             cy = 0;
50449         for (; i < ii; i++) {
50450             var pathi = path[i],
50451                 pathil = pathi.length,
50452                 pathim = path[i - 1],
50453                 pathiml = pathim.length,
50454                 pathip = path[i + 1],
50455                 pathipl = pathip && pathip.length;
50456             if (pathi[0] == "M") {
50457                 mx = pathi[1];
50458                 my = pathi[2];
50459                 j = i + 1;
50460                 while (path[j][0] != "C") {
50461                     j++;
50462                 }
50463                 cx = path[j][5];
50464                 cy = path[j][6];
50465                 newp.push(["M", mx, my]);
50466                 beg = newp.length;
50467                 x = mx;
50468                 y = my;
50469                 continue;
50470             }
50471             if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
50472                 var begl = newp[beg].length;
50473                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
50474                 newp[beg][1] = points.x2;
50475                 newp[beg][2] = points.y2;
50476             }
50477             else if (!pathip || pathip[0] == "M") {
50478                 points = {
50479                     x1: pathi[pathil - 2],
50480                     y1: pathi[pathil - 1]
50481                 };
50482             } else {
50483                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
50484             }
50485             newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
50486             x = points.x2;
50487             y = points.y2;
50488         }
50489         return newp;
50490     },
50491
50492     findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
50493         var t1 = 1 - t;
50494         return {
50495             x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
50496             y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
50497         };
50498     },
50499
50500     snapEnds: function (from, to, stepsMax) {
50501         var step = (to - from) / stepsMax,
50502             level = Math.floor(Math.log(step) / Math.LN10) + 1,
50503             m = Math.pow(10, level),
50504             cur,
50505             modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
50506             interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
50507             stepCount = 0,
50508             value,
50509             weight,
50510             i,
50511             topValue,
50512             topWeight = 1e9,
50513             ln = interval.length;
50514         cur = from = Math.floor(from / m) * m;
50515         for (i = 0; i < ln; i++) {
50516             value = interval[i][0];
50517             weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
50518             if (weight < topWeight) {
50519                 topValue = value;
50520                 topWeight = weight;
50521             }
50522         }
50523         step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
50524         while (cur < to) {
50525             cur += step;
50526             stepCount++;
50527         }
50528         to = +cur.toFixed(10);
50529         return {
50530             from: from,
50531             to: to,
50532             power: level,
50533             step: step,
50534             steps: stepCount
50535         };
50536     },
50537
50538     sorter: function (a, b) {
50539         return a.offset - b.offset;
50540     },
50541
50542     rad: function(degrees) {
50543         return degrees % 360 * Math.PI / 180;
50544     },
50545
50546     degrees: function(radian) {
50547         return radian * 180 / Math.PI % 360;
50548     },
50549
50550     withinBox: function(x, y, bbox) {
50551         bbox = bbox || {};
50552         return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
50553     },
50554
50555     parseGradient: function(gradient) {
50556         var me = this,
50557             type = gradient.type || 'linear',
50558             angle = gradient.angle || 0,
50559             radian = me.radian,
50560             stops = gradient.stops,
50561             stopsArr = [],
50562             stop,
50563             vector,
50564             max,
50565             stopObj;
50566
50567         if (type == 'linear') {
50568             vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
50569             max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
50570             vector[2] *= max;
50571             vector[3] *= max;
50572             if (vector[2] < 0) {
50573                 vector[0] = -vector[2];
50574                 vector[2] = 0;
50575             }
50576             if (vector[3] < 0) {
50577                 vector[1] = -vector[3];
50578                 vector[3] = 0;
50579             }
50580         }
50581
50582         for (stop in stops) {
50583             if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
50584                 stopObj = {
50585                     offset: parseInt(stop, 10),
50586                     color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
50587                     opacity: stops[stop].opacity || 1
50588                 };
50589                 stopsArr.push(stopObj);
50590             }
50591         }
50592         // Sort by pct property
50593         Ext.Array.sort(stopsArr, me.sorter);
50594         if (type == 'linear') {
50595             return {
50596                 id: gradient.id,
50597                 type: type,
50598                 vector: vector,
50599                 stops: stopsArr
50600             };
50601         }
50602         else {
50603             return {
50604                 id: gradient.id,
50605                 type: type,
50606                 centerX: gradient.centerX,
50607                 centerY: gradient.centerY,
50608                 focalX: gradient.focalX,
50609                 focalY: gradient.focalY,
50610                 radius: gradient.radius,
50611                 vector: vector,
50612                 stops: stopsArr
50613             };
50614         }
50615     }
50616 });
50617
50618 /**
50619  * @class Ext.fx.PropertyHandler
50620  * @ignore
50621  */
50622 Ext.define('Ext.fx.PropertyHandler', {
50623
50624     /* Begin Definitions */
50625
50626     requires: ['Ext.draw.Draw'],
50627
50628     statics: {
50629         defaultHandler: {
50630             pixelDefaults: ['width', 'height', 'top', 'left'],
50631             unitRE: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/,
50632
50633             computeDelta: function(from, end, damper, initial, attr) {
50634                 damper = (typeof damper == 'number') ? damper : 1;
50635                 var match = this.unitRE.exec(from),
50636                     start, units;
50637                 if (match) {
50638                     from = match[1];
50639                     units = match[2];
50640                     if (!units && Ext.Array.contains(this.pixelDefaults, attr)) {
50641                         units = 'px';
50642                     }
50643                 }
50644                 from = +from || 0;
50645
50646                 match = this.unitRE.exec(end);
50647                 if (match) {
50648                     end = match[1];
50649                     units = match[2] || units;
50650                 }
50651                 end = +end || 0;
50652                 start = (initial != null) ? initial : from;
50653                 return {
50654                     from: from,
50655                     delta: (end - start) * damper,
50656                     units: units
50657                 };
50658             },
50659
50660             get: function(from, end, damper, initialFrom, attr) {
50661                 var ln = from.length,
50662                     out = [],
50663                     i, initial, res, j, len;
50664                 for (i = 0; i < ln; i++) {
50665                     if (initialFrom) {
50666                         initial = initialFrom[i][1].from;
50667                     }
50668                     if (Ext.isArray(from[i][1]) && Ext.isArray(end)) {
50669                         res = [];
50670                         j = 0;
50671                         len = from[i][1].length;
50672                         for (; j < len; j++) {
50673                             res.push(this.computeDelta(from[i][1][j], end[j], damper, initial, attr));
50674                         }
50675                         out.push([from[i][0], res]);
50676                     }
50677                     else {
50678                         out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
50679                     }
50680                 }
50681                 return out;
50682             },
50683
50684             set: function(values, easing) {
50685                 var ln = values.length,
50686                     out = [],
50687                     i, val, res, len, j;
50688                 for (i = 0; i < ln; i++) {
50689                     val  = values[i][1];
50690                     if (Ext.isArray(val)) {
50691                         res = [];
50692                         j = 0;
50693                         len = val.length;
50694                         for (; j < len; j++) {
50695                             res.push(val[j].from + (val[j].delta * easing) + (val[j].units || 0));
50696                         }
50697                         out.push([values[i][0], res]);
50698                     } else {
50699                         out.push([values[i][0], val.from + (val.delta * easing) + (val.units || 0)]);
50700                     }
50701                 }
50702                 return out;
50703             }
50704         },
50705         color: {
50706             rgbRE: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
50707             hexRE: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
50708             hex3RE: /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
50709
50710             parseColor : function(color, damper) {
50711                 damper = (typeof damper == 'number') ? damper : 1;
50712                 var base,
50713                     out = false,
50714                     match;
50715
50716                 Ext.each([this.hexRE, this.rgbRE, this.hex3RE], function(re, idx) {
50717                     base = (idx % 2 == 0) ? 16 : 10;
50718                     match = re.exec(color);
50719                     if (match && match.length == 4) {
50720                         if (idx == 2) {
50721                             match[1] += match[1];
50722                             match[2] += match[2];
50723                             match[3] += match[3];
50724                         }
50725                         out = {
50726                             red: parseInt(match[1], base),
50727                             green: parseInt(match[2], base),
50728                             blue: parseInt(match[3], base)
50729                         };
50730                         return false;
50731                     }
50732                 });
50733                 return out || color;
50734             },
50735
50736             computeDelta: function(from, end, damper, initial) {
50737                 from = this.parseColor(from);
50738                 end = this.parseColor(end, damper);
50739                 var start = initial ? initial : from,
50740                     tfrom = typeof start,
50741                     tend = typeof end;
50742                 //Extra check for when the color string is not recognized.
50743                 if (tfrom == 'string' ||  tfrom == 'undefined' 
50744                   || tend == 'string' || tend == 'undefined') {
50745                     return end || start;
50746                 }
50747                 return {
50748                     from:  from,
50749                     delta: {
50750                         red: Math.round((end.red - start.red) * damper),
50751                         green: Math.round((end.green - start.green) * damper),
50752                         blue: Math.round((end.blue - start.blue) * damper)
50753                     }
50754                 };
50755             },
50756
50757             get: function(start, end, damper, initialFrom) {
50758                 var ln = start.length,
50759                     out = [],
50760                     i, initial;
50761                 for (i = 0; i < ln; i++) {
50762                     if (initialFrom) {
50763                         initial = initialFrom[i][1].from;
50764                     }
50765                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
50766                 }
50767                 return out;
50768             },
50769
50770             set: function(values, easing) {
50771                 var ln = values.length,
50772                     out = [],
50773                     i, val, parsedString, from, delta;
50774                 for (i = 0; i < ln; i++) {
50775                     val = values[i][1];
50776                     if (val) {
50777                         from = val.from;
50778                         delta = val.delta;
50779                         //multiple checks to reformat the color if it can't recognized by computeDelta.
50780                         val = (typeof val == 'object' && 'red' in val)? 
50781                                 'rgb(' + val.red + ', ' + val.green + ', ' + val.blue + ')' : val;
50782                         val = (typeof val == 'object' && val.length)? val[0] : val;
50783                         if (typeof val == 'undefined') {
50784                             return [];
50785                         }
50786                         parsedString = typeof val == 'string'? val :
50787                             'rgb(' + [
50788                                   (from.red + Math.round(delta.red * easing)) % 256,
50789                                   (from.green + Math.round(delta.green * easing)) % 256,
50790                                   (from.blue + Math.round(delta.blue * easing)) % 256
50791                               ].join(',') + ')';
50792                         out.push([
50793                             values[i][0],
50794                             parsedString
50795                         ]);
50796                     }
50797                 }
50798                 return out;
50799             }
50800         },
50801         object: {
50802             interpolate: function(prop, damper) {
50803                 damper = (typeof damper == 'number') ? damper : 1;
50804                 var out = {},
50805                     p;
50806                 for(p in prop) {
50807                     out[p] = parseInt(prop[p], 10) * damper;
50808                 }
50809                 return out;
50810             },
50811
50812             computeDelta: function(from, end, damper, initial) {
50813                 from = this.interpolate(from);
50814                 end = this.interpolate(end, damper);
50815                 var start = initial ? initial : from,
50816                     delta = {},
50817                     p;
50818
50819                 for(p in end) {
50820                     delta[p] = end[p] - start[p];
50821                 }
50822                 return {
50823                     from:  from,
50824                     delta: delta
50825                 };
50826             },
50827
50828             get: function(start, end, damper, initialFrom) {
50829                 var ln = start.length,
50830                     out = [],
50831                     i, initial;
50832                 for (i = 0; i < ln; i++) {
50833                     if (initialFrom) {
50834                         initial = initialFrom[i][1].from;
50835                     }
50836                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
50837                 }
50838                 return out;
50839             },
50840
50841             set: function(values, easing) {
50842                 var ln = values.length,
50843                     out = [],
50844                     outObject = {},
50845                     i, from, delta, val, p;
50846                 for (i = 0; i < ln; i++) {
50847                     val  = values[i][1];
50848                     from = val.from;
50849                     delta = val.delta;
50850                     for (p in from) {
50851                         outObject[p] = Math.round(from[p] + delta[p] * easing);
50852                     }
50853                     out.push([
50854                         values[i][0],
50855                         outObject
50856                     ]);
50857                 }
50858                 return out;
50859             }
50860         },
50861
50862         path: {
50863             computeDelta: function(from, end, damper, initial) {
50864                 damper = (typeof damper == 'number') ? damper : 1;
50865                 var start;
50866                 from = +from || 0;
50867                 end = +end || 0;
50868                 start = (initial != null) ? initial : from;
50869                 return {
50870                     from: from,
50871                     delta: (end - start) * damper
50872                 };
50873             },
50874
50875             forcePath: function(path) {
50876                 if (!Ext.isArray(path) && !Ext.isArray(path[0])) {
50877                     path = Ext.draw.Draw.parsePathString(path);
50878                 }
50879                 return path;
50880             },
50881
50882             get: function(start, end, damper, initialFrom) {
50883                 var endPath = this.forcePath(end),
50884                     out = [],
50885                     startLn = start.length,
50886                     startPathLn, pointsLn, i, deltaPath, initial, j, k, path, startPath;
50887                 for (i = 0; i < startLn; i++) {
50888                     startPath = this.forcePath(start[i][1]);
50889
50890                     deltaPath = Ext.draw.Draw.interpolatePaths(startPath, endPath);
50891                     startPath = deltaPath[0];
50892                     endPath = deltaPath[1];
50893
50894                     startPathLn = startPath.length;
50895                     path = [];
50896                     for (j = 0; j < startPathLn; j++) {
50897                         deltaPath = [startPath[j][0]];
50898                         pointsLn = startPath[j].length;
50899                         for (k = 1; k < pointsLn; k++) {
50900                             initial = initialFrom && initialFrom[0][1][j][k].from;
50901                             deltaPath.push(this.computeDelta(startPath[j][k], endPath[j][k], damper, initial));
50902                         }
50903                         path.push(deltaPath);
50904                     }
50905                     out.push([start[i][0], path]);
50906                 }
50907                 return out;
50908             },
50909
50910             set: function(values, easing) {
50911                 var ln = values.length,
50912                     out = [],
50913                     i, j, k, newPath, calcPath, deltaPath, deltaPathLn, pointsLn;
50914                 for (i = 0; i < ln; i++) {
50915                     deltaPath = values[i][1];
50916                     newPath = [];
50917                     deltaPathLn = deltaPath.length;
50918                     for (j = 0; j < deltaPathLn; j++) {
50919                         calcPath = [deltaPath[j][0]];
50920                         pointsLn = deltaPath[j].length;
50921                         for (k = 1; k < pointsLn; k++) {
50922                             calcPath.push(deltaPath[j][k].from + deltaPath[j][k].delta * easing);
50923                         }
50924                         newPath.push(calcPath.join(','));
50925                     }
50926                     out.push([values[i][0], newPath.join(',')]);
50927                 }
50928                 return out;
50929             }
50930         }
50931         /* End Definitions */
50932     }
50933 }, function() {
50934     Ext.each([
50935         'outlineColor',
50936         'backgroundColor',
50937         'borderColor',
50938         'borderTopColor',
50939         'borderRightColor', 
50940         'borderBottomColor', 
50941         'borderLeftColor',
50942         'fill',
50943         'stroke'
50944     ], function(prop) {
50945         this[prop] = this.color;
50946     }, this);
50947 });
50948 /**
50949  * @class Ext.fx.Anim
50950  * 
50951  * This class manages animation for a specific {@link #target}. The animation allows
50952  * animation of various properties on the target, such as size, position, color and others.
50953  * 
50954  * ## Starting Conditions
50955  * The starting conditions for the animation are provided by the {@link #from} configuration.
50956  * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
50957  * property is not defined, the starting value for that property will be read directly from the target.
50958  * 
50959  * ## End Conditions
50960  * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
50961  * the final values once the animations has finished. The values in the {@link #from} can mirror
50962  * those in the {@link #to} configuration to provide a starting point.
50963  * 
50964  * ## Other Options
50965  *  - {@link #duration}: Specifies the time period of the animation.
50966  *  - {@link #easing}: Specifies the easing of the animation.
50967  *  - {@link #iterations}: Allows the animation to repeat a number of times.
50968  *  - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
50969  * 
50970  * ## Example Code
50971  * 
50972  *     var myComponent = Ext.create('Ext.Component', {
50973  *         renderTo: document.body,
50974  *         width: 200,
50975  *         height: 200,
50976  *         style: 'border: 1px solid red;'
50977  *     });
50978  *     
50979  *     new Ext.fx.Anim({
50980  *         target: myComponent,
50981  *         duration: 1000,
50982  *         from: {
50983  *             width: 400 //starting width 400
50984  *         },
50985  *         to: {
50986  *             width: 300, //end width 300
50987  *             height: 300 // end width 300
50988  *         }
50989  *     });
50990  */
50991 Ext.define('Ext.fx.Anim', {
50992
50993     /* Begin Definitions */
50994
50995     mixins: {
50996         observable: 'Ext.util.Observable'
50997     },
50998
50999     requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
51000
51001     /* End Definitions */
51002
51003     isAnimation: true,
51004     /**
51005      * @cfg {Number} duration
51006      * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
51007      * specified, then each animate will take the same duration for each iteration.
51008      */
51009     duration: 250,
51010
51011     /**
51012      * @cfg {Number} delay
51013      * Time to delay before starting the animation. Defaults to 0.
51014      */
51015     delay: 0,
51016
51017     /* private used to track a delayed starting time */
51018     delayStart: 0,
51019
51020     /**
51021      * @cfg {Boolean} dynamic
51022      * 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.
51023      */
51024     dynamic: false,
51025
51026     /**
51027      * @cfg {String} easing
51028 This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
51029 speed over its duration. 
51030
51031          -backIn
51032          -backOut
51033          -bounceIn
51034          -bounceOut
51035          -ease
51036          -easeIn
51037          -easeOut
51038          -easeInOut
51039          -elasticIn
51040          -elasticOut
51041          -cubic-bezier(x1, y1, x2, y2)
51042
51043 Note that cubic-bezier will create a custom easing curve following the CSS3 transition-timing-function specification `{@link http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag}`. The four values specify points P1 and P2 of the curve
51044 as (x1, y1, x2, y2). All values must be in the range [0, 1] or the definition is invalid.
51045      * @markdown
51046      */
51047     easing: 'ease',
51048
51049      /**
51050       * @cfg {Object} keyframes
51051       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
51052       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
51053       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
51054       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
51055       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
51056  <pre><code>
51057 keyframes : {
51058     '0%': {
51059         left: 100
51060     },
51061     '40%': {
51062         left: 150
51063     },
51064     '60%': {
51065         left: 75
51066     },
51067     '100%': {
51068         left: 100
51069     }
51070 }
51071  </code></pre>
51072       */
51073
51074     /**
51075      * @private
51076      */
51077     damper: 1,
51078
51079     /**
51080      * @private
51081      */
51082     bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
51083
51084     /**
51085      * Run the animation from the end to the beginning
51086      * Defaults to false.
51087      * @cfg {Boolean} reverse
51088      */
51089     reverse: false,
51090
51091     /**
51092      * Flag to determine if the animation has started
51093      * @property running
51094      * @type boolean
51095      */
51096     running: false,
51097
51098     /**
51099      * Flag to determine if the animation is paused. Only set this to true if you need to
51100      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
51101      * @property paused
51102      * @type boolean
51103      */
51104     paused: false,
51105
51106     /**
51107      * Number of times to execute the animation. Defaults to 1.
51108      * @cfg {int} iterations
51109      */
51110     iterations: 1,
51111
51112     /**
51113      * Used in conjunction with iterations to reverse the animation each time an iteration completes.
51114      * @cfg {Boolean} alternate
51115      * Defaults to false.
51116      */
51117     alternate: false,
51118
51119     /**
51120      * Current iteration the animation is running.
51121      * @property currentIteration
51122      * @type int
51123      */
51124     currentIteration: 0,
51125
51126     /**
51127      * Starting time of the animation.
51128      * @property startTime
51129      * @type Date
51130      */
51131     startTime: 0,
51132
51133     /**
51134      * Contains a cache of the interpolators to be used.
51135      * @private
51136      * @property propHandlers
51137      * @type Object
51138      */
51139
51140     /**
51141      * @cfg {String/Object} target
51142      * The {@link Ext.fx.target.Target} to apply the animation to.  This should only be specified when creating an Ext.fx.Anim directly.
51143      * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
51144      * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
51145      * automatically.
51146      */
51147
51148     /**
51149      * @cfg {Object} from
51150      * An object containing property/value pairs for the beginning of the animation.  If not specified, the current state of the
51151      * Ext.fx.target will be used. For example:
51152 <pre><code>
51153 from : {
51154     opacity: 0,       // Transparent
51155     color: '#ffffff', // White
51156     left: 0
51157 }
51158 </code></pre>
51159      */
51160
51161     /**
51162      * @cfg {Object} to
51163      * An object containing property/value pairs for the end of the animation. For example:
51164  <pre><code>
51165  to : {
51166      opacity: 1,       // Opaque
51167      color: '#00ff00', // Green
51168      left: 500
51169  }
51170  </code></pre>
51171      */
51172
51173     // @private
51174     constructor: function(config) {
51175         var me = this;
51176         config = config || {};
51177         // If keyframes are passed, they really want an Animator instead.
51178         if (config.keyframes) {
51179             return Ext.create('Ext.fx.Animator', config);
51180         }
51181         config = Ext.apply(me, config);
51182         if (me.from === undefined) {
51183             me.from = {};
51184         }
51185         me.propHandlers = {};
51186         me.config = config;
51187         me.target = Ext.fx.Manager.createTarget(me.target);
51188         me.easingFn = Ext.fx.Easing[me.easing];
51189         me.target.dynamic = me.dynamic;
51190
51191         // If not a pre-defined curve, try a cubic-bezier
51192         if (!me.easingFn) {
51193             me.easingFn = String(me.easing).match(me.bezierRE);
51194             if (me.easingFn && me.easingFn.length == 5) {
51195                 var curve = me.easingFn;
51196                 me.easingFn = Ext.fx.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
51197             }
51198         }
51199         me.id = Ext.id(null, 'ext-anim-');
51200         Ext.fx.Manager.addAnim(me);
51201         me.addEvents(
51202             /**
51203              * @event beforeanimate
51204              * Fires before the animation starts. A handler can return false to cancel the animation.
51205              * @param {Ext.fx.Anim} this
51206              */
51207             'beforeanimate',
51208              /**
51209               * @event afteranimate
51210               * Fires when the animation is complete.
51211               * @param {Ext.fx.Anim} this
51212               * @param {Date} startTime
51213               */
51214             'afteranimate',
51215              /**
51216               * @event lastframe
51217               * Fires when the animation's last frame has been set.
51218               * @param {Ext.fx.Anim} this
51219               * @param {Date} startTime
51220               */
51221             'lastframe'
51222         );
51223         me.mixins.observable.constructor.call(me, config);
51224         if (config.callback) {
51225             me.on('afteranimate', config.callback, config.scope);
51226         }
51227         return me;
51228     },
51229
51230     /**
51231      * @private
51232      * Helper to the target
51233      */
51234     setAttr: function(attr, value) {
51235         return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
51236     },
51237
51238     /*
51239      * @private
51240      * Set up the initial currentAttrs hash.
51241      */
51242     initAttrs: function() {
51243         var me = this,
51244             from = me.from,
51245             to = me.to,
51246             initialFrom = me.initialFrom || {},
51247             out = {},
51248             start, end, propHandler, attr;
51249
51250         for (attr in to) {
51251             if (to.hasOwnProperty(attr)) {
51252                 start = me.target.getAttr(attr, from[attr]);
51253                 end = to[attr];
51254                 // Use default (numeric) property handler
51255                 if (!Ext.fx.PropertyHandler[attr]) {
51256                     if (Ext.isObject(end)) {
51257                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
51258                     } else {
51259                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
51260                     }
51261                 }
51262                 // Use custom handler
51263                 else {
51264                     propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
51265                 }
51266                 out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
51267             }
51268         }
51269         me.currentAttrs = out;
51270     },
51271
51272     /*
51273      * @private
51274      * Fires beforeanimate and sets the running flag.
51275      */
51276     start: function(startTime) {
51277         var me = this,
51278             delay = me.delay,
51279             delayStart = me.delayStart,
51280             delayDelta;
51281         if (delay) {
51282             if (!delayStart) {
51283                 me.delayStart = startTime;
51284                 return;
51285             }
51286             else {
51287                 delayDelta = startTime - delayStart;
51288                 if (delayDelta < delay) {
51289                     return;
51290                 }
51291                 else {
51292                     // Compensate for frame delay;
51293                     startTime = new Date(delayStart.getTime() + delay);
51294                 }
51295             }
51296         }
51297         if (me.fireEvent('beforeanimate', me) !== false) {
51298             me.startTime = startTime;
51299             if (!me.paused && !me.currentAttrs) {
51300                 me.initAttrs();
51301             }
51302             me.running = true;
51303         }
51304     },
51305
51306     /*
51307      * @private
51308      * Calculate attribute value at the passed timestamp.
51309      * @returns a hash of the new attributes.
51310      */
51311     runAnim: function(elapsedTime) {
51312         var me = this,
51313             attrs = me.currentAttrs,
51314             duration = me.duration,
51315             easingFn = me.easingFn,
51316             propHandlers = me.propHandlers,
51317             ret = {},
51318             easing, values, attr, lastFrame;
51319
51320         if (elapsedTime >= duration) {
51321             elapsedTime = duration;
51322             lastFrame = true;
51323         }
51324         if (me.reverse) {
51325             elapsedTime = duration - elapsedTime;
51326         }
51327
51328         for (attr in attrs) {
51329             if (attrs.hasOwnProperty(attr)) {
51330                 values = attrs[attr];
51331                 easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
51332                 ret[attr] = propHandlers[attr].set(values, easing);
51333             }
51334         }
51335         return ret;
51336     },
51337
51338     /*
51339      * @private
51340      * Perform lastFrame cleanup and handle iterations
51341      * @returns a hash of the new attributes.
51342      */
51343     lastFrame: function() {
51344         var me = this,
51345             iter = me.iterations,
51346             iterCount = me.currentIteration;
51347
51348         iterCount++;
51349         if (iterCount < iter) {
51350             if (me.alternate) {
51351                 me.reverse = !me.reverse;
51352             }
51353             me.startTime = new Date();
51354             me.currentIteration = iterCount;
51355             // Turn off paused for CSS3 Transitions
51356             me.paused = false;
51357         }
51358         else {
51359             me.currentIteration = 0;
51360             me.end();
51361             me.fireEvent('lastframe', me, me.startTime);
51362         }
51363     },
51364
51365     /*
51366      * Fire afteranimate event and end the animation. Usually called automatically when the
51367      * animation reaches its final frame, but can also be called manually to pre-emptively
51368      * stop and destroy the running animation.
51369      */
51370     end: function() {
51371         var me = this;
51372         me.startTime = 0;
51373         me.paused = false;
51374         me.running = false;
51375         Ext.fx.Manager.removeAnim(me);
51376         me.fireEvent('afteranimate', me, me.startTime);
51377     }
51378 });
51379 // Set flag to indicate that Fx is available. Class might not be available immediately.
51380 Ext.enableFx = true;
51381
51382 /*
51383  * This is a derivative of the similarly named class in the YUI Library.
51384  * The original license:
51385  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
51386  * Code licensed under the BSD License:
51387  * http://developer.yahoo.net/yui/license.txt
51388  */
51389
51390
51391 /**
51392  * @class Ext.dd.DragDrop
51393  * Defines the interface and base operation of items that that can be
51394  * dragged or can be drop targets.  It was designed to be extended, overriding
51395  * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
51396  * Up to three html elements can be associated with a DragDrop instance:
51397  * <ul>
51398  * <li>linked element: the element that is passed into the constructor.
51399  * This is the element which defines the boundaries for interaction with
51400  * other DragDrop objects.</li>
51401  * <li>handle element(s): The drag operation only occurs if the element that
51402  * was clicked matches a handle element.  By default this is the linked
51403  * element, but there are times that you will want only a portion of the
51404  * linked element to initiate the drag operation, and the setHandleElId()
51405  * method provides a way to define this.</li>
51406  * <li>drag element: this represents the element that would be moved along
51407  * with the cursor during a drag operation.  By default, this is the linked
51408  * element itself as in {@link Ext.dd.DD}.  setDragElId() lets you define
51409  * a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
51410  * </li>
51411  * </ul>
51412  * This class should not be instantiated until the onload event to ensure that
51413  * the associated elements are available.
51414  * The following would define a DragDrop obj that would interact with any
51415  * other DragDrop obj in the "group1" group:
51416  * <pre>
51417  *  dd = new Ext.dd.DragDrop("div1", "group1");
51418  * </pre>
51419  * Since none of the event handlers have been implemented, nothing would
51420  * actually happen if you were to run the code above.  Normally you would
51421  * override this class or one of the default implementations, but you can
51422  * also override the methods you want on an instance of the class...
51423  * <pre>
51424  *  dd.onDragDrop = function(e, id) {
51425  *  &nbsp;&nbsp;alert("dd was dropped on " + id);
51426  *  }
51427  * </pre>
51428  * @constructor
51429  * @param {String} id of the element that is linked to this instance
51430  * @param {String} sGroup the group of related DragDrop objects
51431  * @param {object} config an object containing configurable attributes
51432  *                Valid properties for DragDrop:
51433  *                    padding, isTarget, maintainOffset, primaryButtonOnly
51434  */
51435
51436 Ext.define('Ext.dd.DragDrop', {
51437     requires: ['Ext.dd.DragDropManager'],
51438     constructor: function(id, sGroup, config) {
51439         if(id) {
51440             this.init(id, sGroup, config);
51441         }
51442     },
51443     
51444     /**
51445      * Set to false to enable a DragDrop object to fire drag events while dragging
51446      * over its own Element. Defaults to true - DragDrop objects do not by default
51447      * fire drag events to themselves.
51448      * @property ignoreSelf
51449      * @type Boolean
51450      */
51451
51452     /**
51453      * The id of the element associated with this object.  This is what we
51454      * refer to as the "linked element" because the size and position of
51455      * this element is used to determine when the drag and drop objects have
51456      * interacted.
51457      * @property id
51458      * @type String
51459      */
51460     id: null,
51461
51462     /**
51463      * Configuration attributes passed into the constructor
51464      * @property config
51465      * @type object
51466      */
51467     config: null,
51468
51469     /**
51470      * The id of the element that will be dragged.  By default this is same
51471      * as the linked element, but could be changed to another element. Ex:
51472      * Ext.dd.DDProxy
51473      * @property dragElId
51474      * @type String
51475      * @private
51476      */
51477     dragElId: null,
51478
51479     /**
51480      * The ID of the element that initiates the drag operation.  By default
51481      * this is the linked element, but could be changed to be a child of this
51482      * element.  This lets us do things like only starting the drag when the
51483      * header element within the linked html element is clicked.
51484      * @property handleElId
51485      * @type String
51486      * @private
51487      */
51488     handleElId: null,
51489
51490     /**
51491      * An object who's property names identify HTML tags to be considered invalid as drag handles.
51492      * A non-null property value identifies the tag as invalid. Defaults to the 
51493      * following value which prevents drag operations from being initiated by &lt;a> elements:<pre><code>
51494 {
51495     A: "A"
51496 }</code></pre>
51497      * @property invalidHandleTypes
51498      * @type Object
51499      */
51500     invalidHandleTypes: null,
51501
51502     /**
51503      * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
51504      * A non-null property value identifies the ID as invalid. For example, to prevent
51505      * dragging from being initiated on element ID "foo", use:<pre><code>
51506 {
51507     foo: true
51508 }</code></pre>
51509      * @property invalidHandleIds
51510      * @type Object
51511      */
51512     invalidHandleIds: null,
51513
51514     /**
51515      * An Array of CSS class names for elements to be considered in valid as drag handles.
51516      * @property invalidHandleClasses
51517      * @type Array
51518      */
51519     invalidHandleClasses: null,
51520
51521     /**
51522      * The linked element's absolute X position at the time the drag was
51523      * started
51524      * @property startPageX
51525      * @type int
51526      * @private
51527      */
51528     startPageX: 0,
51529
51530     /**
51531      * The linked element's absolute X position at the time the drag was
51532      * started
51533      * @property startPageY
51534      * @type int
51535      * @private
51536      */
51537     startPageY: 0,
51538
51539     /**
51540      * The group defines a logical collection of DragDrop objects that are
51541      * related.  Instances only get events when interacting with other
51542      * DragDrop object in the same group.  This lets us define multiple
51543      * groups using a single DragDrop subclass if we want.
51544      * @property groups
51545      * @type object An object in the format {'group1':true, 'group2':true}
51546      */
51547     groups: null,
51548
51549     /**
51550      * Individual drag/drop instances can be locked.  This will prevent
51551      * onmousedown start drag.
51552      * @property locked
51553      * @type boolean
51554      * @private
51555      */
51556     locked: false,
51557
51558     /**
51559      * Lock this instance
51560      * @method lock
51561      */
51562     lock: function() {
51563         this.locked = true;
51564     },
51565
51566     /**
51567      * When set to true, other DD objects in cooperating DDGroups do not receive
51568      * notification events when this DD object is dragged over them. Defaults to false.
51569      * @property moveOnly
51570      * @type boolean
51571      */
51572     moveOnly: false,
51573
51574     /**
51575      * Unlock this instace
51576      * @method unlock
51577      */
51578     unlock: function() {
51579         this.locked = false;
51580     },
51581
51582     /**
51583      * By default, all instances can be a drop target.  This can be disabled by
51584      * setting isTarget to false.
51585      * @property isTarget
51586      * @type boolean
51587      */
51588     isTarget: true,
51589
51590     /**
51591      * The padding configured for this drag and drop object for calculating
51592      * the drop zone intersection with this object.
51593      * An array containing the 4 padding values: [top, right, bottom, left]
51594      * @property {[int]} padding
51595      */
51596     padding: null,
51597
51598     /**
51599      * Cached reference to the linked element
51600      * @property _domRef
51601      * @private
51602      */
51603     _domRef: null,
51604
51605     /**
51606      * Internal typeof flag
51607      * @property __ygDragDrop
51608      * @private
51609      */
51610     __ygDragDrop: true,
51611
51612     /**
51613      * Set to true when horizontal contraints are applied
51614      * @property constrainX
51615      * @type boolean
51616      * @private
51617      */
51618     constrainX: false,
51619
51620     /**
51621      * Set to true when vertical contraints are applied
51622      * @property constrainY
51623      * @type boolean
51624      * @private
51625      */
51626     constrainY: false,
51627
51628     /**
51629      * The left constraint
51630      * @property minX
51631      * @type int
51632      * @private
51633      */
51634     minX: 0,
51635
51636     /**
51637      * The right constraint
51638      * @property maxX
51639      * @type int
51640      * @private
51641      */
51642     maxX: 0,
51643
51644     /**
51645      * The up constraint
51646      * @property minY
51647      * @type int
51648      * @private
51649      */
51650     minY: 0,
51651
51652     /**
51653      * The down constraint
51654      * @property maxY
51655      * @type int
51656      * @private
51657      */
51658     maxY: 0,
51659
51660     /**
51661      * Maintain offsets when we resetconstraints.  Set to true when you want
51662      * the position of the element relative to its parent to stay the same
51663      * when the page changes
51664      *
51665      * @property maintainOffset
51666      * @type boolean
51667      */
51668     maintainOffset: false,
51669
51670     /**
51671      * Array of pixel locations the element will snap to if we specified a
51672      * horizontal graduation/interval.  This array is generated automatically
51673      * when you define a tick interval.
51674      * @property {[int]} xTicks
51675      */
51676     xTicks: null,
51677
51678     /**
51679      * Array of pixel locations the element will snap to if we specified a
51680      * vertical graduation/interval.  This array is generated automatically
51681      * when you define a tick interval.
51682      * @property {[int]} yTicks
51683      */
51684     yTicks: null,
51685
51686     /**
51687      * By default the drag and drop instance will only respond to the primary
51688      * button click (left button for a right-handed mouse).  Set to true to
51689      * allow drag and drop to start with any mouse click that is propogated
51690      * by the browser
51691      * @property primaryButtonOnly
51692      * @type boolean
51693      */
51694     primaryButtonOnly: true,
51695
51696     /**
51697      * The available property is false until the linked dom element is accessible.
51698      * @property available
51699      * @type boolean
51700      */
51701     available: false,
51702
51703     /**
51704      * By default, drags can only be initiated if the mousedown occurs in the
51705      * region the linked element is.  This is done in part to work around a
51706      * bug in some browsers that mis-report the mousedown if the previous
51707      * mouseup happened outside of the window.  This property is set to true
51708      * if outer handles are defined.
51709      *
51710      * @property hasOuterHandles
51711      * @type boolean
51712      * @default false
51713      */
51714     hasOuterHandles: false,
51715
51716     /**
51717      * Code that executes immediately before the startDrag event
51718      * @method b4StartDrag
51719      * @private
51720      */
51721     b4StartDrag: function(x, y) { },
51722
51723     /**
51724      * Abstract method called after a drag/drop object is clicked
51725      * and the drag or mousedown time thresholds have beeen met.
51726      * @method startDrag
51727      * @param {int} X click location
51728      * @param {int} Y click location
51729      */
51730     startDrag: function(x, y) { /* override this */ },
51731
51732     /**
51733      * Code that executes immediately before the onDrag event
51734      * @method b4Drag
51735      * @private
51736      */
51737     b4Drag: function(e) { },
51738
51739     /**
51740      * Abstract method called during the onMouseMove event while dragging an
51741      * object.
51742      * @method onDrag
51743      * @param {Event} e the mousemove event
51744      */
51745     onDrag: function(e) { /* override this */ },
51746
51747     /**
51748      * Abstract method called when this element fist begins hovering over
51749      * another DragDrop obj
51750      * @method onDragEnter
51751      * @param {Event} e the mousemove event
51752      * @param {String|DragDrop[]} id In POINT mode, the element
51753      * id this is hovering over.  In INTERSECT mode, an array of one or more
51754      * dragdrop items being hovered over.
51755      */
51756     onDragEnter: function(e, id) { /* override this */ },
51757
51758     /**
51759      * Code that executes immediately before the onDragOver event
51760      * @method b4DragOver
51761      * @private
51762      */
51763     b4DragOver: function(e) { },
51764
51765     /**
51766      * Abstract method called when this element is hovering over another
51767      * DragDrop obj
51768      * @method onDragOver
51769      * @param {Event} e the mousemove event
51770      * @param {String|DragDrop[]} id In POINT mode, the element
51771      * id this is hovering over.  In INTERSECT mode, an array of dd items
51772      * being hovered over.
51773      */
51774     onDragOver: function(e, id) { /* override this */ },
51775
51776     /**
51777      * Code that executes immediately before the onDragOut event
51778      * @method b4DragOut
51779      * @private
51780      */
51781     b4DragOut: function(e) { },
51782
51783     /**
51784      * Abstract method called when we are no longer hovering over an element
51785      * @method onDragOut
51786      * @param {Event} e the mousemove event
51787      * @param {String|DragDrop[]} id In POINT mode, the element
51788      * id this was hovering over.  In INTERSECT mode, an array of dd items
51789      * that the mouse is no longer over.
51790      */
51791     onDragOut: function(e, id) { /* override this */ },
51792
51793     /**
51794      * Code that executes immediately before the onDragDrop event
51795      * @method b4DragDrop
51796      * @private
51797      */
51798     b4DragDrop: function(e) { },
51799
51800     /**
51801      * Abstract method called when this item is dropped on another DragDrop
51802      * obj
51803      * @method onDragDrop
51804      * @param {Event} e the mouseup event
51805      * @param {String|DragDrop[]} id In POINT mode, the element
51806      * id this was dropped on.  In INTERSECT mode, an array of dd items this
51807      * was dropped on.
51808      */
51809     onDragDrop: function(e, id) { /* override this */ },
51810
51811     /**
51812      * Abstract method called when this item is dropped on an area with no
51813      * drop target
51814      * @method onInvalidDrop
51815      * @param {Event} e the mouseup event
51816      */
51817     onInvalidDrop: function(e) { /* override this */ },
51818
51819     /**
51820      * Code that executes immediately before the endDrag event
51821      * @method b4EndDrag
51822      * @private
51823      */
51824     b4EndDrag: function(e) { },
51825
51826     /**
51827      * Fired when we are done dragging the object
51828      * @method endDrag
51829      * @param {Event} e the mouseup event
51830      */
51831     endDrag: function(e) { /* override this */ },
51832
51833     /**
51834      * Code executed immediately before the onMouseDown event
51835      * @method b4MouseDown
51836      * @param {Event} e the mousedown event
51837      * @private
51838      */
51839     b4MouseDown: function(e) {  },
51840
51841     /**
51842      * Event handler that fires when a drag/drop obj gets a mousedown
51843      * @method onMouseDown
51844      * @param {Event} e the mousedown event
51845      */
51846     onMouseDown: function(e) { /* override this */ },
51847
51848     /**
51849      * Event handler that fires when a drag/drop obj gets a mouseup
51850      * @method onMouseUp
51851      * @param {Event} e the mouseup event
51852      */
51853     onMouseUp: function(e) { /* override this */ },
51854
51855     /**
51856      * Override the onAvailable method to do what is needed after the initial
51857      * position was determined.
51858      * @method onAvailable
51859      */
51860     onAvailable: function () {
51861     },
51862
51863     /**
51864      * Provides default constraint padding to "constrainTo" elements (defaults to {left: 0, right:0, top:0, bottom:0}).
51865      * @type Object
51866      */
51867     defaultPadding: {
51868         left: 0,
51869         right: 0,
51870         top: 0,
51871         bottom: 0
51872     },
51873
51874     /**
51875      * Initializes the drag drop object's constraints to restrict movement to a certain element.
51876  *
51877  * Usage:
51878  <pre><code>
51879  var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
51880                 { dragElId: "existingProxyDiv" });
51881  dd.startDrag = function(){
51882      this.constrainTo("parent-id");
51883  };
51884  </code></pre>
51885  * Or you can initalize it using the {@link Ext.core.Element} object:
51886  <pre><code>
51887  Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
51888      startDrag : function(){
51889          this.constrainTo("parent-id");
51890      }
51891  });
51892  </code></pre>
51893      * @param {Mixed} constrainTo The element to constrain to.
51894      * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
51895      * and can be either a number for symmetrical padding (4 would be equal to {left:4, right:4, top:4, bottom:4}) or
51896      * an object containing the sides to pad. For example: {right:10, bottom:10}
51897      * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
51898      */
51899     constrainTo : function(constrainTo, pad, inContent){
51900         if(Ext.isNumber(pad)){
51901             pad = {left: pad, right:pad, top:pad, bottom:pad};
51902         }
51903         pad = pad || this.defaultPadding;
51904         var b = Ext.get(this.getEl()).getBox(),
51905             ce = Ext.get(constrainTo),
51906             s = ce.getScroll(),
51907             c, 
51908             cd = ce.dom;
51909         if(cd == document.body){
51910             c = { x: s.left, y: s.top, width: Ext.core.Element.getViewWidth(), height: Ext.core.Element.getViewHeight()};
51911         }else{
51912             var xy = ce.getXY();
51913             c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
51914         }
51915
51916
51917         var topSpace = b.y - c.y,
51918             leftSpace = b.x - c.x;
51919
51920         this.resetConstraints();
51921         this.setXConstraint(leftSpace - (pad.left||0), // left
51922                 c.width - leftSpace - b.width - (pad.right||0), //right
51923                                 this.xTickSize
51924         );
51925         this.setYConstraint(topSpace - (pad.top||0), //top
51926                 c.height - topSpace - b.height - (pad.bottom||0), //bottom
51927                                 this.yTickSize
51928         );
51929     },
51930
51931     /**
51932      * Returns a reference to the linked element
51933      * @method getEl
51934      * @return {HTMLElement} the html element
51935      */
51936     getEl: function() {
51937         if (!this._domRef) {
51938             this._domRef = Ext.getDom(this.id);
51939         }
51940
51941         return this._domRef;
51942     },
51943
51944     /**
51945      * Returns a reference to the actual element to drag.  By default this is
51946      * the same as the html element, but it can be assigned to another
51947      * element. An example of this can be found in Ext.dd.DDProxy
51948      * @method getDragEl
51949      * @return {HTMLElement} the html element
51950      */
51951     getDragEl: function() {
51952         return Ext.getDom(this.dragElId);
51953     },
51954
51955     /**
51956      * Sets up the DragDrop object.  Must be called in the constructor of any
51957      * Ext.dd.DragDrop subclass
51958      * @method init
51959      * @param id the id of the linked element
51960      * @param {String} sGroup the group of related items
51961      * @param {object} config configuration attributes
51962      */
51963     init: function(id, sGroup, config) {
51964         this.initTarget(id, sGroup, config);
51965         Ext.EventManager.on(this.id, "mousedown", this.handleMouseDown, this);
51966         // Ext.EventManager.on(this.id, "selectstart", Event.preventDefault);
51967     },
51968
51969     /**
51970      * Initializes Targeting functionality only... the object does not
51971      * get a mousedown handler.
51972      * @method initTarget
51973      * @param id the id of the linked element
51974      * @param {String} sGroup the group of related items
51975      * @param {object} config configuration attributes
51976      */
51977     initTarget: function(id, sGroup, config) {
51978
51979         // configuration attributes
51980         this.config = config || {};
51981
51982         // create a local reference to the drag and drop manager
51983         this.DDMInstance = Ext.dd.DragDropManager;
51984         // initialize the groups array
51985         this.groups = {};
51986
51987         // assume that we have an element reference instead of an id if the
51988         // parameter is not a string
51989         if (typeof id !== "string") {
51990             id = Ext.id(id);
51991         }
51992
51993         // set the id
51994         this.id = id;
51995
51996         // add to an interaction group
51997         this.addToGroup((sGroup) ? sGroup : "default");
51998
51999         // We don't want to register this as the handle with the manager
52000         // so we just set the id rather than calling the setter.
52001         this.handleElId = id;
52002
52003         // the linked element is the element that gets dragged by default
52004         this.setDragElId(id);
52005
52006         // by default, clicked anchors will not start drag operations.
52007         this.invalidHandleTypes = { A: "A" };
52008         this.invalidHandleIds = {};
52009         this.invalidHandleClasses = [];
52010
52011         this.applyConfig();
52012
52013         this.handleOnAvailable();
52014     },
52015
52016     /**
52017      * Applies the configuration parameters that were passed into the constructor.
52018      * This is supposed to happen at each level through the inheritance chain.  So
52019      * a DDProxy implentation will execute apply config on DDProxy, DD, and
52020      * DragDrop in order to get all of the parameters that are available in
52021      * each object.
52022      * @method applyConfig
52023      */
52024     applyConfig: function() {
52025
52026         // configurable properties:
52027         //    padding, isTarget, maintainOffset, primaryButtonOnly
52028         this.padding           = this.config.padding || [0, 0, 0, 0];
52029         this.isTarget          = (this.config.isTarget !== false);
52030         this.maintainOffset    = (this.config.maintainOffset);
52031         this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
52032
52033     },
52034
52035     /**
52036      * Executed when the linked element is available
52037      * @method handleOnAvailable
52038      * @private
52039      */
52040     handleOnAvailable: function() {
52041         this.available = true;
52042         this.resetConstraints();
52043         this.onAvailable();
52044     },
52045
52046      /**
52047      * Configures the padding for the target zone in px.  Effectively expands
52048      * (or reduces) the virtual object size for targeting calculations.
52049      * Supports css-style shorthand; if only one parameter is passed, all sides
52050      * will have that padding, and if only two are passed, the top and bottom
52051      * will have the first param, the left and right the second.
52052      * @method setPadding
52053      * @param {int} iTop    Top pad
52054      * @param {int} iRight  Right pad
52055      * @param {int} iBot    Bot pad
52056      * @param {int} iLeft   Left pad
52057      */
52058     setPadding: function(iTop, iRight, iBot, iLeft) {
52059         // this.padding = [iLeft, iRight, iTop, iBot];
52060         if (!iRight && 0 !== iRight) {
52061             this.padding = [iTop, iTop, iTop, iTop];
52062         } else if (!iBot && 0 !== iBot) {
52063             this.padding = [iTop, iRight, iTop, iRight];
52064         } else {
52065             this.padding = [iTop, iRight, iBot, iLeft];
52066         }
52067     },
52068
52069     /**
52070      * Stores the initial placement of the linked element.
52071      * @method setInitPosition
52072      * @param {int} diffX   the X offset, default 0
52073      * @param {int} diffY   the Y offset, default 0
52074      */
52075     setInitPosition: function(diffX, diffY) {
52076         var el = this.getEl();
52077
52078         if (!this.DDMInstance.verifyEl(el)) {
52079             return;
52080         }
52081
52082         var dx = diffX || 0;
52083         var dy = diffY || 0;
52084
52085         var p = Ext.core.Element.getXY( el );
52086
52087         this.initPageX = p[0] - dx;
52088         this.initPageY = p[1] - dy;
52089
52090         this.lastPageX = p[0];
52091         this.lastPageY = p[1];
52092
52093         this.setStartPosition(p);
52094     },
52095
52096     /**
52097      * Sets the start position of the element.  This is set when the obj
52098      * is initialized, the reset when a drag is started.
52099      * @method setStartPosition
52100      * @param pos current position (from previous lookup)
52101      * @private
52102      */
52103     setStartPosition: function(pos) {
52104         var p = pos || Ext.core.Element.getXY( this.getEl() );
52105         this.deltaSetXY = null;
52106
52107         this.startPageX = p[0];
52108         this.startPageY = p[1];
52109     },
52110
52111     /**
52112      * Add this instance to a group of related drag/drop objects.  All
52113      * instances belong to at least one group, and can belong to as many
52114      * groups as needed.
52115      * @method addToGroup
52116      * @param sGroup {string} the name of the group
52117      */
52118     addToGroup: function(sGroup) {
52119         this.groups[sGroup] = true;
52120         this.DDMInstance.regDragDrop(this, sGroup);
52121     },
52122
52123     /**
52124      * Remove's this instance from the supplied interaction group
52125      * @method removeFromGroup
52126      * @param {string}  sGroup  The group to drop
52127      */
52128     removeFromGroup: function(sGroup) {
52129         if (this.groups[sGroup]) {
52130             delete this.groups[sGroup];
52131         }
52132
52133         this.DDMInstance.removeDDFromGroup(this, sGroup);
52134     },
52135
52136     /**
52137      * Allows you to specify that an element other than the linked element
52138      * will be moved with the cursor during a drag
52139      * @method setDragElId
52140      * @param id {string} the id of the element that will be used to initiate the drag
52141      */
52142     setDragElId: function(id) {
52143         this.dragElId = id;
52144     },
52145
52146     /**
52147      * Allows you to specify a child of the linked element that should be
52148      * used to initiate the drag operation.  An example of this would be if
52149      * you have a content div with text and links.  Clicking anywhere in the
52150      * content area would normally start the drag operation.  Use this method
52151      * to specify that an element inside of the content div is the element
52152      * that starts the drag operation.
52153      * @method setHandleElId
52154      * @param id {string} the id of the element that will be used to
52155      * initiate the drag.
52156      */
52157     setHandleElId: function(id) {
52158         if (typeof id !== "string") {
52159             id = Ext.id(id);
52160         }
52161         this.handleElId = id;
52162         this.DDMInstance.regHandle(this.id, id);
52163     },
52164
52165     /**
52166      * Allows you to set an element outside of the linked element as a drag
52167      * handle
52168      * @method setOuterHandleElId
52169      * @param id the id of the element that will be used to initiate the drag
52170      */
52171     setOuterHandleElId: function(id) {
52172         if (typeof id !== "string") {
52173             id = Ext.id(id);
52174         }
52175         Ext.EventManager.on(id, "mousedown", this.handleMouseDown, this);
52176         this.setHandleElId(id);
52177
52178         this.hasOuterHandles = true;
52179     },
52180
52181     /**
52182      * Remove all drag and drop hooks for this element
52183      * @method unreg
52184      */
52185     unreg: function() {
52186         Ext.EventManager.un(this.id, "mousedown", this.handleMouseDown, this);
52187         this._domRef = null;
52188         this.DDMInstance._remove(this);
52189     },
52190
52191     destroy : function(){
52192         this.unreg();
52193     },
52194
52195     /**
52196      * Returns true if this instance is locked, or the drag drop mgr is locked
52197      * (meaning that all drag/drop is disabled on the page.)
52198      * @method isLocked
52199      * @return {boolean} true if this obj or all drag/drop is locked, else
52200      * false
52201      */
52202     isLocked: function() {
52203         return (this.DDMInstance.isLocked() || this.locked);
52204     },
52205
52206     /**
52207      * Fired when this object is clicked
52208      * @method handleMouseDown
52209      * @param {Event} e
52210      * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
52211      * @private
52212      */
52213     handleMouseDown: function(e, oDD){
52214         if (this.primaryButtonOnly && e.button != 0) {
52215             return;
52216         }
52217
52218         if (this.isLocked()) {
52219             return;
52220         }
52221
52222         this.DDMInstance.refreshCache(this.groups);
52223
52224         var pt = e.getPoint();
52225         if (!this.hasOuterHandles && !this.DDMInstance.isOverTarget(pt, this) )  {
52226         } else {
52227             if (this.clickValidator(e)) {
52228                 // set the initial element position
52229                 this.setStartPosition();
52230                 this.b4MouseDown(e);
52231                 this.onMouseDown(e);
52232
52233                 this.DDMInstance.handleMouseDown(e, this);
52234
52235                 this.DDMInstance.stopEvent(e);
52236             } else {
52237
52238
52239             }
52240         }
52241     },
52242
52243     clickValidator: function(e) {
52244         var target = e.getTarget();
52245         return ( this.isValidHandleChild(target) &&
52246                     (this.id == this.handleElId ||
52247                         this.DDMInstance.handleWasClicked(target, this.id)) );
52248     },
52249
52250     /**
52251      * Allows you to specify a tag name that should not start a drag operation
52252      * when clicked.  This is designed to facilitate embedding links within a
52253      * drag handle that do something other than start the drag.
52254      * @method addInvalidHandleType
52255      * @param {string} tagName the type of element to exclude
52256      */
52257     addInvalidHandleType: function(tagName) {
52258         var type = tagName.toUpperCase();
52259         this.invalidHandleTypes[type] = type;
52260     },
52261
52262     /**
52263      * Lets you to specify an element id for a child of a drag handle
52264      * that should not initiate a drag
52265      * @method addInvalidHandleId
52266      * @param {string} id the element id of the element you wish to ignore
52267      */
52268     addInvalidHandleId: function(id) {
52269         if (typeof id !== "string") {
52270             id = Ext.id(id);
52271         }
52272         this.invalidHandleIds[id] = id;
52273     },
52274
52275     /**
52276      * Lets you specify a css class of elements that will not initiate a drag
52277      * @method addInvalidHandleClass
52278      * @param {string} cssClass the class of the elements you wish to ignore
52279      */
52280     addInvalidHandleClass: function(cssClass) {
52281         this.invalidHandleClasses.push(cssClass);
52282     },
52283
52284     /**
52285      * Unsets an excluded tag name set by addInvalidHandleType
52286      * @method removeInvalidHandleType
52287      * @param {string} tagName the type of element to unexclude
52288      */
52289     removeInvalidHandleType: function(tagName) {
52290         var type = tagName.toUpperCase();
52291         // this.invalidHandleTypes[type] = null;
52292         delete this.invalidHandleTypes[type];
52293     },
52294
52295     /**
52296      * Unsets an invalid handle id
52297      * @method removeInvalidHandleId
52298      * @param {string} id the id of the element to re-enable
52299      */
52300     removeInvalidHandleId: function(id) {
52301         if (typeof id !== "string") {
52302             id = Ext.id(id);
52303         }
52304         delete this.invalidHandleIds[id];
52305     },
52306
52307     /**
52308      * Unsets an invalid css class
52309      * @method removeInvalidHandleClass
52310      * @param {string} cssClass the class of the element(s) you wish to
52311      * re-enable
52312      */
52313     removeInvalidHandleClass: function(cssClass) {
52314         for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
52315             if (this.invalidHandleClasses[i] == cssClass) {
52316                 delete this.invalidHandleClasses[i];
52317             }
52318         }
52319     },
52320
52321     /**
52322      * Checks the tag exclusion list to see if this click should be ignored
52323      * @method isValidHandleChild
52324      * @param {HTMLElement} node the HTMLElement to evaluate
52325      * @return {boolean} true if this is a valid tag type, false if not
52326      */
52327     isValidHandleChild: function(node) {
52328
52329         var valid = true;
52330         // var n = (node.nodeName == "#text") ? node.parentNode : node;
52331         var nodeName;
52332         try {
52333             nodeName = node.nodeName.toUpperCase();
52334         } catch(e) {
52335             nodeName = node.nodeName;
52336         }
52337         valid = valid && !this.invalidHandleTypes[nodeName];
52338         valid = valid && !this.invalidHandleIds[node.id];
52339
52340         for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
52341             valid = !Ext.fly(node).hasCls(this.invalidHandleClasses[i]);
52342         }
52343
52344
52345         return valid;
52346
52347     },
52348
52349     /**
52350      * Create the array of horizontal tick marks if an interval was specified
52351      * in setXConstraint().
52352      * @method setXTicks
52353      * @private
52354      */
52355     setXTicks: function(iStartX, iTickSize) {
52356         this.xTicks = [];
52357         this.xTickSize = iTickSize;
52358
52359         var tickMap = {};
52360
52361         for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
52362             if (!tickMap[i]) {
52363                 this.xTicks[this.xTicks.length] = i;
52364                 tickMap[i] = true;
52365             }
52366         }
52367
52368         for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
52369             if (!tickMap[i]) {
52370                 this.xTicks[this.xTicks.length] = i;
52371                 tickMap[i] = true;
52372             }
52373         }
52374
52375         Ext.Array.sort(this.xTicks, this.DDMInstance.numericSort);
52376     },
52377
52378     /**
52379      * Create the array of vertical tick marks if an interval was specified in
52380      * setYConstraint().
52381      * @method setYTicks
52382      * @private
52383      */
52384     setYTicks: function(iStartY, iTickSize) {
52385         this.yTicks = [];
52386         this.yTickSize = iTickSize;
52387
52388         var tickMap = {};
52389
52390         for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
52391             if (!tickMap[i]) {
52392                 this.yTicks[this.yTicks.length] = i;
52393                 tickMap[i] = true;
52394             }
52395         }
52396
52397         for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
52398             if (!tickMap[i]) {
52399                 this.yTicks[this.yTicks.length] = i;
52400                 tickMap[i] = true;
52401             }
52402         }
52403
52404         Ext.Array.sort(this.yTicks, this.DDMInstance.numericSort);
52405     },
52406
52407     /**
52408      * By default, the element can be dragged any place on the screen.  Use
52409      * this method to limit the horizontal travel of the element.  Pass in
52410      * 0,0 for the parameters if you want to lock the drag to the y axis.
52411      * @method setXConstraint
52412      * @param {int} iLeft the number of pixels the element can move to the left
52413      * @param {int} iRight the number of pixels the element can move to the
52414      * right
52415      * @param {int} iTickSize optional parameter for specifying that the
52416      * element
52417      * should move iTickSize pixels at a time.
52418      */
52419     setXConstraint: function(iLeft, iRight, iTickSize) {
52420         this.leftConstraint = iLeft;
52421         this.rightConstraint = iRight;
52422
52423         this.minX = this.initPageX - iLeft;
52424         this.maxX = this.initPageX + iRight;
52425         if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
52426
52427         this.constrainX = true;
52428     },
52429
52430     /**
52431      * Clears any constraints applied to this instance.  Also clears ticks
52432      * since they can't exist independent of a constraint at this time.
52433      * @method clearConstraints
52434      */
52435     clearConstraints: function() {
52436         this.constrainX = false;
52437         this.constrainY = false;
52438         this.clearTicks();
52439     },
52440
52441     /**
52442      * Clears any tick interval defined for this instance
52443      * @method clearTicks
52444      */
52445     clearTicks: function() {
52446         this.xTicks = null;
52447         this.yTicks = null;
52448         this.xTickSize = 0;
52449         this.yTickSize = 0;
52450     },
52451
52452     /**
52453      * By default, the element can be dragged any place on the screen.  Set
52454      * this to limit the vertical travel of the element.  Pass in 0,0 for the
52455      * parameters if you want to lock the drag to the x axis.
52456      * @method setYConstraint
52457      * @param {int} iUp the number of pixels the element can move up
52458      * @param {int} iDown the number of pixels the element can move down
52459      * @param {int} iTickSize optional parameter for specifying that the
52460      * element should move iTickSize pixels at a time.
52461      */
52462     setYConstraint: function(iUp, iDown, iTickSize) {
52463         this.topConstraint = iUp;
52464         this.bottomConstraint = iDown;
52465
52466         this.minY = this.initPageY - iUp;
52467         this.maxY = this.initPageY + iDown;
52468         if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
52469
52470         this.constrainY = true;
52471
52472     },
52473
52474     /**
52475      * resetConstraints must be called if you manually reposition a dd element.
52476      * @method resetConstraints
52477      * @param {boolean} maintainOffset
52478      */
52479     resetConstraints: function() {
52480         // Maintain offsets if necessary
52481         if (this.initPageX || this.initPageX === 0) {
52482             // figure out how much this thing has moved
52483             var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
52484             var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
52485
52486             this.setInitPosition(dx, dy);
52487
52488         // This is the first time we have detected the element's position
52489         } else {
52490             this.setInitPosition();
52491         }
52492
52493         if (this.constrainX) {
52494             this.setXConstraint( this.leftConstraint,
52495                                  this.rightConstraint,
52496                                  this.xTickSize        );
52497         }
52498
52499         if (this.constrainY) {
52500             this.setYConstraint( this.topConstraint,
52501                                  this.bottomConstraint,
52502                                  this.yTickSize         );
52503         }
52504     },
52505
52506     /**
52507      * Normally the drag element is moved pixel by pixel, but we can specify
52508      * that it move a number of pixels at a time.  This method resolves the
52509      * location when we have it set up like this.
52510      * @method getTick
52511      * @param {int} val where we want to place the object
52512      * @param {int[]} tickArray sorted array of valid points
52513      * @return {int} the closest tick
52514      * @private
52515      */
52516     getTick: function(val, tickArray) {
52517         if (!tickArray) {
52518             // If tick interval is not defined, it is effectively 1 pixel,
52519             // so we return the value passed to us.
52520             return val;
52521         } else if (tickArray[0] >= val) {
52522             // The value is lower than the first tick, so we return the first
52523             // tick.
52524             return tickArray[0];
52525         } else {
52526             for (var i=0, len=tickArray.length; i<len; ++i) {
52527                 var next = i + 1;
52528                 if (tickArray[next] && tickArray[next] >= val) {
52529                     var diff1 = val - tickArray[i];
52530                     var diff2 = tickArray[next] - val;
52531                     return (diff2 > diff1) ? tickArray[i] : tickArray[next];
52532                 }
52533             }
52534
52535             // The value is larger than the last tick, so we return the last
52536             // tick.
52537             return tickArray[tickArray.length - 1];
52538         }
52539     },
52540
52541     /**
52542      * toString method
52543      * @method toString
52544      * @return {string} string representation of the dd obj
52545      */
52546     toString: function() {
52547         return ("DragDrop " + this.id);
52548     }
52549
52550 });
52551 /*
52552  * This is a derivative of the similarly named class in the YUI Library.
52553  * The original license:
52554  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
52555  * Code licensed under the BSD License:
52556  * http://developer.yahoo.net/yui/license.txt
52557  */
52558
52559
52560 /**
52561  * @class Ext.dd.DD
52562  * A DragDrop implementation where the linked element follows the
52563  * mouse cursor during a drag.
52564  * @extends Ext.dd.DragDrop
52565  * @constructor
52566  * @param {String} id the id of the linked element
52567  * @param {String} sGroup the group of related DragDrop items
52568  * @param {object} config an object containing configurable attributes
52569  *                Valid properties for DD:
52570  *                    scroll
52571  */
52572
52573 Ext.define('Ext.dd.DD', {
52574     extend: 'Ext.dd.DragDrop',
52575     requires: ['Ext.dd.DragDropManager'],
52576     constructor: function(id, sGroup, config) {
52577         if (id) {
52578             this.init(id, sGroup, config);
52579         }
52580     },
52581
52582     /**
52583      * When set to true, the utility automatically tries to scroll the browser
52584      * window when a drag and drop element is dragged near the viewport boundary.
52585      * Defaults to true.
52586      * @property scroll
52587      * @type boolean
52588      */
52589     scroll: true,
52590
52591     /**
52592      * Sets the pointer offset to the distance between the linked element's top
52593      * left corner and the location the element was clicked
52594      * @method autoOffset
52595      * @param {int} iPageX the X coordinate of the click
52596      * @param {int} iPageY the Y coordinate of the click
52597      */
52598     autoOffset: function(iPageX, iPageY) {
52599         var x = iPageX - this.startPageX;
52600         var y = iPageY - this.startPageY;
52601         this.setDelta(x, y);
52602     },
52603
52604     /**
52605      * Sets the pointer offset.  You can call this directly to force the
52606      * offset to be in a particular location (e.g., pass in 0,0 to set it
52607      * to the center of the object)
52608      * @method setDelta
52609      * @param {int} iDeltaX the distance from the left
52610      * @param {int} iDeltaY the distance from the top
52611      */
52612     setDelta: function(iDeltaX, iDeltaY) {
52613         this.deltaX = iDeltaX;
52614         this.deltaY = iDeltaY;
52615     },
52616
52617     /**
52618      * Sets the drag element to the location of the mousedown or click event,
52619      * maintaining the cursor location relative to the location on the element
52620      * that was clicked.  Override this if you want to place the element in a
52621      * location other than where the cursor is.
52622      * @method setDragElPos
52623      * @param {int} iPageX the X coordinate of the mousedown or drag event
52624      * @param {int} iPageY the Y coordinate of the mousedown or drag event
52625      */
52626     setDragElPos: function(iPageX, iPageY) {
52627         // the first time we do this, we are going to check to make sure
52628         // the element has css positioning
52629
52630         var el = this.getDragEl();
52631         this.alignElWithMouse(el, iPageX, iPageY);
52632     },
52633
52634     /**
52635      * Sets the element to the location of the mousedown or click event,
52636      * maintaining the cursor location relative to the location on the element
52637      * that was clicked.  Override this if you want to place the element in a
52638      * location other than where the cursor is.
52639      * @method alignElWithMouse
52640      * @param {HTMLElement} el the element to move
52641      * @param {int} iPageX the X coordinate of the mousedown or drag event
52642      * @param {int} iPageY the Y coordinate of the mousedown or drag event
52643      */
52644     alignElWithMouse: function(el, iPageX, iPageY) {
52645         var oCoord = this.getTargetCoord(iPageX, iPageY),
52646             fly = el.dom ? el : Ext.fly(el, '_dd'),
52647             elSize = fly.getSize(),
52648             EL = Ext.core.Element,
52649             vpSize;
52650
52651         if (!this.deltaSetXY) {
52652             vpSize = this.cachedViewportSize = { width: EL.getDocumentWidth(), height: EL.getDocumentHeight() };
52653             var aCoord = [
52654                 Math.max(0, Math.min(oCoord.x, vpSize.width - elSize.width)),
52655                 Math.max(0, Math.min(oCoord.y, vpSize.height - elSize.height))
52656             ];
52657             fly.setXY(aCoord);
52658             var newLeft = fly.getLeft(true);
52659             var newTop  = fly.getTop(true);
52660             this.deltaSetXY = [newLeft - oCoord.x, newTop - oCoord.y];
52661         } else {
52662             vpSize = this.cachedViewportSize;
52663             fly.setLeftTop(
52664                 Math.max(0, Math.min(oCoord.x + this.deltaSetXY[0], vpSize.width - elSize.width)),
52665                 Math.max(0, Math.min(oCoord.y + this.deltaSetXY[1], vpSize.height - elSize.height))
52666             );
52667         }
52668
52669         this.cachePosition(oCoord.x, oCoord.y);
52670         this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
52671         return oCoord;
52672     },
52673
52674     /**
52675      * Saves the most recent position so that we can reset the constraints and
52676      * tick marks on-demand.  We need to know this so that we can calculate the
52677      * number of pixels the element is offset from its original position.
52678      * @method cachePosition
52679      * @param iPageX the current x position (optional, this just makes it so we
52680      * don't have to look it up again)
52681      * @param iPageY the current y position (optional, this just makes it so we
52682      * don't have to look it up again)
52683      */
52684     cachePosition: function(iPageX, iPageY) {
52685         if (iPageX) {
52686             this.lastPageX = iPageX;
52687             this.lastPageY = iPageY;
52688         } else {
52689             var aCoord = Ext.core.Element.getXY(this.getEl());
52690             this.lastPageX = aCoord[0];
52691             this.lastPageY = aCoord[1];
52692         }
52693     },
52694
52695     /**
52696      * Auto-scroll the window if the dragged object has been moved beyond the
52697      * visible window boundary.
52698      * @method autoScroll
52699      * @param {int} x the drag element's x position
52700      * @param {int} y the drag element's y position
52701      * @param {int} h the height of the drag element
52702      * @param {int} w the width of the drag element
52703      * @private
52704      */
52705     autoScroll: function(x, y, h, w) {
52706
52707         if (this.scroll) {
52708             // The client height
52709             var clientH = Ext.core.Element.getViewHeight();
52710
52711             // The client width
52712             var clientW = Ext.core.Element.getViewWidth();
52713
52714             // The amt scrolled down
52715             var st = this.DDMInstance.getScrollTop();
52716
52717             // The amt scrolled right
52718             var sl = this.DDMInstance.getScrollLeft();
52719
52720             // Location of the bottom of the element
52721             var bot = h + y;
52722
52723             // Location of the right of the element
52724             var right = w + x;
52725
52726             // The distance from the cursor to the bottom of the visible area,
52727             // adjusted so that we don't scroll if the cursor is beyond the
52728             // element drag constraints
52729             var toBot = (clientH + st - y - this.deltaY);
52730
52731             // The distance from the cursor to the right of the visible area
52732             var toRight = (clientW + sl - x - this.deltaX);
52733
52734
52735             // How close to the edge the cursor must be before we scroll
52736             // var thresh = (document.all) ? 100 : 40;
52737             var thresh = 40;
52738
52739             // How many pixels to scroll per autoscroll op.  This helps to reduce
52740             // clunky scrolling. IE is more sensitive about this ... it needs this
52741             // value to be higher.
52742             var scrAmt = (document.all) ? 80 : 30;
52743
52744             // Scroll down if we are near the bottom of the visible page and the
52745             // obj extends below the crease
52746             if ( bot > clientH && toBot < thresh ) {
52747                 window.scrollTo(sl, st + scrAmt);
52748             }
52749
52750             // Scroll up if the window is scrolled down and the top of the object
52751             // goes above the top border
52752             if ( y < st && st > 0 && y - st < thresh ) {
52753                 window.scrollTo(sl, st - scrAmt);
52754             }
52755
52756             // Scroll right if the obj is beyond the right border and the cursor is
52757             // near the border.
52758             if ( right > clientW && toRight < thresh ) {
52759                 window.scrollTo(sl + scrAmt, st);
52760             }
52761
52762             // Scroll left if the window has been scrolled to the right and the obj
52763             // extends past the left border
52764             if ( x < sl && sl > 0 && x - sl < thresh ) {
52765                 window.scrollTo(sl - scrAmt, st);
52766             }
52767         }
52768     },
52769
52770     /**
52771      * Finds the location the element should be placed if we want to move
52772      * it to where the mouse location less the click offset would place us.
52773      * @method getTargetCoord
52774      * @param {int} iPageX the X coordinate of the click
52775      * @param {int} iPageY the Y coordinate of the click
52776      * @return an object that contains the coordinates (Object.x and Object.y)
52777      * @private
52778      */
52779     getTargetCoord: function(iPageX, iPageY) {
52780         var x = iPageX - this.deltaX;
52781         var y = iPageY - this.deltaY;
52782
52783         if (this.constrainX) {
52784             if (x < this.minX) {
52785                 x = this.minX;
52786             }
52787             if (x > this.maxX) {
52788                 x = this.maxX;
52789             }
52790         }
52791
52792         if (this.constrainY) {
52793             if (y < this.minY) {
52794                 y = this.minY;
52795             }
52796             if (y > this.maxY) {
52797                 y = this.maxY;
52798             }
52799         }
52800
52801         x = this.getTick(x, this.xTicks);
52802         y = this.getTick(y, this.yTicks);
52803
52804
52805         return {x: x, y: y};
52806     },
52807
52808     /**
52809      * Sets up config options specific to this class. Overrides
52810      * Ext.dd.DragDrop, but all versions of this method through the
52811      * inheritance chain are called
52812      */
52813     applyConfig: function() {
52814         this.callParent();
52815         this.scroll = (this.config.scroll !== false);
52816     },
52817
52818     /**
52819      * Event that fires prior to the onMouseDown event.  Overrides
52820      * Ext.dd.DragDrop.
52821      */
52822     b4MouseDown: function(e) {
52823         // this.resetConstraints();
52824         this.autoOffset(e.getPageX(), e.getPageY());
52825     },
52826
52827     /**
52828      * Event that fires prior to the onDrag event.  Overrides
52829      * Ext.dd.DragDrop.
52830      */
52831     b4Drag: function(e) {
52832         this.setDragElPos(e.getPageX(), e.getPageY());
52833     },
52834
52835     toString: function() {
52836         return ("DD " + this.id);
52837     }
52838
52839     //////////////////////////////////////////////////////////////////////////
52840     // Debugging ygDragDrop events that can be overridden
52841     //////////////////////////////////////////////////////////////////////////
52842     /*
52843     startDrag: function(x, y) {
52844     },
52845
52846     onDrag: function(e) {
52847     },
52848
52849     onDragEnter: function(e, id) {
52850     },
52851
52852     onDragOver: function(e, id) {
52853     },
52854
52855     onDragOut: function(e, id) {
52856     },
52857
52858     onDragDrop: function(e, id) {
52859     },
52860
52861     endDrag: function(e) {
52862     }
52863
52864     */
52865
52866 });
52867
52868 /*
52869  * This is a derivative of the similarly named class in the YUI Library.
52870  * The original license:
52871  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
52872  * Code licensed under the BSD License:
52873  * http://developer.yahoo.net/yui/license.txt
52874  */
52875
52876 /**
52877  * @class Ext.dd.DDProxy
52878  * A DragDrop implementation that inserts an empty, bordered div into
52879  * the document that follows the cursor during drag operations.  At the time of
52880  * the click, the frame div is resized to the dimensions of the linked html
52881  * element, and moved to the exact location of the linked element.
52882  *
52883  * References to the "frame" element refer to the single proxy element that
52884  * was created to be dragged in place of all DDProxy elements on the
52885  * page.
52886  *
52887  * @extends Ext.dd.DD
52888  * @constructor
52889  * @param {String} id the id of the linked html element
52890  * @param {String} sGroup the group of related DragDrop objects
52891  * @param {object} config an object containing configurable attributes
52892  *                Valid properties for DDProxy in addition to those in DragDrop:
52893  *                   resizeFrame, centerFrame, dragElId
52894  */
52895 Ext.define('Ext.dd.DDProxy', {
52896     extend: 'Ext.dd.DD',
52897
52898     statics: {
52899         /**
52900          * The default drag frame div id
52901          * @property Ext.dd.DDProxy.dragElId
52902          * @type String
52903          * @static
52904          */
52905         dragElId: "ygddfdiv"
52906     },
52907
52908     constructor: function(id, sGroup, config) {
52909         if (id) {
52910             this.init(id, sGroup, config);
52911             this.initFrame();
52912         }
52913     },
52914
52915     /**
52916      * By default we resize the drag frame to be the same size as the element
52917      * we want to drag (this is to get the frame effect).  We can turn it off
52918      * if we want a different behavior.
52919      * @property resizeFrame
52920      * @type boolean
52921      */
52922     resizeFrame: true,
52923
52924     /**
52925      * By default the frame is positioned exactly where the drag element is, so
52926      * we use the cursor offset provided by Ext.dd.DD.  Another option that works only if
52927      * you do not have constraints on the obj is to have the drag frame centered
52928      * around the cursor.  Set centerFrame to true for this effect.
52929      * @property centerFrame
52930      * @type boolean
52931      */
52932     centerFrame: false,
52933
52934     /**
52935      * Creates the proxy element if it does not yet exist
52936      * @method createFrame
52937      */
52938     createFrame: function() {
52939         var self = this;
52940         var body = document.body;
52941
52942         if (!body || !body.firstChild) {
52943             setTimeout( function() { self.createFrame(); }, 50 );
52944             return;
52945         }
52946
52947         var div = this.getDragEl();
52948
52949         if (!div) {
52950             div    = document.createElement("div");
52951             div.id = this.dragElId;
52952             var s  = div.style;
52953
52954             s.position   = "absolute";
52955             s.visibility = "hidden";
52956             s.cursor     = "move";
52957             s.border     = "2px solid #aaa";
52958             s.zIndex     = 999;
52959
52960             // appendChild can blow up IE if invoked prior to the window load event
52961             // while rendering a table.  It is possible there are other scenarios
52962             // that would cause this to happen as well.
52963             body.insertBefore(div, body.firstChild);
52964         }
52965     },
52966
52967     /**
52968      * Initialization for the drag frame element.  Must be called in the
52969      * constructor of all subclasses
52970      * @method initFrame
52971      */
52972     initFrame: function() {
52973         this.createFrame();
52974     },
52975
52976     applyConfig: function() {
52977         this.callParent();
52978
52979         this.resizeFrame = (this.config.resizeFrame !== false);
52980         this.centerFrame = (this.config.centerFrame);
52981         this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
52982     },
52983
52984     /**
52985      * Resizes the drag frame to the dimensions of the clicked object, positions
52986      * it over the object, and finally displays it
52987      * @method showFrame
52988      * @param {int} iPageX X click position
52989      * @param {int} iPageY Y click position
52990      * @private
52991      */
52992     showFrame: function(iPageX, iPageY) {
52993         var el = this.getEl();
52994         var dragEl = this.getDragEl();
52995         var s = dragEl.style;
52996
52997         this._resizeProxy();
52998
52999         if (this.centerFrame) {
53000             this.setDelta( Math.round(parseInt(s.width,  10)/2),
53001                            Math.round(parseInt(s.height, 10)/2) );
53002         }
53003
53004         this.setDragElPos(iPageX, iPageY);
53005
53006         Ext.fly(dragEl).show();
53007     },
53008
53009     /**
53010      * The proxy is automatically resized to the dimensions of the linked
53011      * element when a drag is initiated, unless resizeFrame is set to false
53012      * @method _resizeProxy
53013      * @private
53014      */
53015     _resizeProxy: function() {
53016         if (this.resizeFrame) {
53017             var el = this.getEl();
53018             Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
53019         }
53020     },
53021
53022     // overrides Ext.dd.DragDrop
53023     b4MouseDown: function(e) {
53024         var x = e.getPageX();
53025         var y = e.getPageY();
53026         this.autoOffset(x, y);
53027         this.setDragElPos(x, y);
53028     },
53029
53030     // overrides Ext.dd.DragDrop
53031     b4StartDrag: function(x, y) {
53032         // show the drag frame
53033         this.showFrame(x, y);
53034     },
53035
53036     // overrides Ext.dd.DragDrop
53037     b4EndDrag: function(e) {
53038         Ext.fly(this.getDragEl()).hide();
53039     },
53040
53041     // overrides Ext.dd.DragDrop
53042     // By default we try to move the element to the last location of the frame.
53043     // This is so that the default behavior mirrors that of Ext.dd.DD.
53044     endDrag: function(e) {
53045
53046         var lel = this.getEl();
53047         var del = this.getDragEl();
53048
53049         // Show the drag frame briefly so we can get its position
53050         del.style.visibility = "";
53051
53052         this.beforeMove();
53053         // Hide the linked element before the move to get around a Safari
53054         // rendering bug.
53055         lel.style.visibility = "hidden";
53056         Ext.dd.DDM.moveToEl(lel, del);
53057         del.style.visibility = "hidden";
53058         lel.style.visibility = "";
53059
53060         this.afterDrag();
53061     },
53062
53063     beforeMove : function(){
53064
53065     },
53066
53067     afterDrag : function(){
53068
53069     },
53070
53071     toString: function() {
53072         return ("DDProxy " + this.id);
53073     }
53074
53075 });
53076
53077 /**
53078  * @class Ext.dd.DragSource
53079  * @extends Ext.dd.DDProxy
53080  * A simple class that provides the basic implementation needed to make any element draggable.
53081  * @constructor
53082  * @param {Mixed} el The container element
53083  * @param {Object} config
53084  */
53085 Ext.define('Ext.dd.DragSource', {
53086     extend: 'Ext.dd.DDProxy',
53087     requires: [
53088         'Ext.dd.StatusProxy',
53089         'Ext.dd.DragDropManager'
53090     ],
53091
53092     /**
53093      * @cfg {String} ddGroup
53094      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
53095      * interact with other drag drop objects in the same group (defaults to undefined).
53096      */
53097
53098     /**
53099      * @cfg {String} dropAllowed
53100      * The CSS class returned to the drag source when drop is allowed (defaults to "x-dd-drop-ok").
53101      */
53102
53103     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
53104     /**
53105      * @cfg {String} dropNotAllowed
53106      * The CSS class returned to the drag source when drop is not allowed (defaults to "x-dd-drop-nodrop").
53107      */
53108     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
53109
53110     /**
53111      * @cfg {Boolean} animRepair
53112      * Defaults to true. If true, animates the proxy element back to the position of the handle element used to trigger the drag.
53113      */
53114     animRepair: true,
53115
53116     /**
53117      * @cfg {String} repairHighlightColor The color to use when visually highlighting the drag source in the afterRepair
53118      * method after a failed drop (defaults to 'c3daf9' - light blue). The color must be a 6 digit hex value, without
53119      * a preceding '#'.
53120      */
53121     repairHighlightColor: 'c3daf9',
53122
53123     constructor: function(el, config) {
53124         this.el = Ext.get(el);
53125         if(!this.dragData){
53126             this.dragData = {};
53127         }
53128
53129         Ext.apply(this, config);
53130
53131         if(!this.proxy){
53132             this.proxy = Ext.create('Ext.dd.StatusProxy', {
53133                 animRepair: this.animRepair
53134             });
53135         }
53136         this.callParent([this.el.dom, this.ddGroup || this.group,
53137               {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true}]);
53138
53139         this.dragging = false;
53140     },
53141
53142     /**
53143      * Returns the data object associated with this drag source
53144      * @return {Object} data An object containing arbitrary data
53145      */
53146     getDragData : function(e){
53147         return this.dragData;
53148     },
53149
53150     // private
53151     onDragEnter : function(e, id){
53152         var target = Ext.dd.DragDropManager.getDDById(id);
53153         this.cachedTarget = target;
53154         if (this.beforeDragEnter(target, e, id) !== false) {
53155             if (target.isNotifyTarget) {
53156                 var status = target.notifyEnter(this, e, this.dragData);
53157                 this.proxy.setStatus(status);
53158             } else {
53159                 this.proxy.setStatus(this.dropAllowed);
53160             }
53161
53162             if (this.afterDragEnter) {
53163                 /**
53164                  * An empty function by default, but provided so that you can perform a custom action
53165                  * when the dragged item enters the drop target by providing an implementation.
53166                  * @param {Ext.dd.DragDrop} target The drop target
53167                  * @param {Event} e The event object
53168                  * @param {String} id The id of the dragged element
53169                  * @method afterDragEnter
53170                  */
53171                 this.afterDragEnter(target, e, id);
53172             }
53173         }
53174     },
53175
53176     /**
53177      * An empty function by default, but provided so that you can perform a custom action
53178      * before the dragged item enters the drop target and optionally cancel the onDragEnter.
53179      * @param {Ext.dd.DragDrop} target The drop target
53180      * @param {Event} e The event object
53181      * @param {String} id The id of the dragged element
53182      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
53183      */
53184     beforeDragEnter: function(target, e, id) {
53185         return true;
53186     },
53187
53188     // private
53189     alignElWithMouse: function() {
53190         this.callParent(arguments);
53191         this.proxy.sync();
53192     },
53193
53194     // private
53195     onDragOver: function(e, id) {
53196         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
53197         if (this.beforeDragOver(target, e, id) !== false) {
53198             if(target.isNotifyTarget){
53199                 var status = target.notifyOver(this, e, this.dragData);
53200                 this.proxy.setStatus(status);
53201             }
53202
53203             if (this.afterDragOver) {
53204                 /**
53205                  * An empty function by default, but provided so that you can perform a custom action
53206                  * while the dragged item is over the drop target by providing an implementation.
53207                  * @param {Ext.dd.DragDrop} target The drop target
53208                  * @param {Event} e The event object
53209                  * @param {String} id The id of the dragged element
53210                  * @method afterDragOver
53211                  */
53212                 this.afterDragOver(target, e, id);
53213             }
53214         }
53215     },
53216
53217     /**
53218      * An empty function by default, but provided so that you can perform a custom action
53219      * while the dragged item is over the drop target and optionally cancel the onDragOver.
53220      * @param {Ext.dd.DragDrop} target The drop target
53221      * @param {Event} e The event object
53222      * @param {String} id The id of the dragged element
53223      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
53224      */
53225     beforeDragOver: function(target, e, id) {
53226         return true;
53227     },
53228
53229     // private
53230     onDragOut: function(e, id) {
53231         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
53232         if (this.beforeDragOut(target, e, id) !== false) {
53233             if (target.isNotifyTarget) {
53234                 target.notifyOut(this, e, this.dragData);
53235             }
53236             this.proxy.reset();
53237             if (this.afterDragOut) {
53238                 /**
53239                  * An empty function by default, but provided so that you can perform a custom action
53240                  * after the dragged item is dragged out of the target without dropping.
53241                  * @param {Ext.dd.DragDrop} target The drop target
53242                  * @param {Event} e The event object
53243                  * @param {String} id The id of the dragged element
53244                  * @method afterDragOut
53245                  */
53246                 this.afterDragOut(target, e, id);
53247             }
53248         }
53249         this.cachedTarget = null;
53250     },
53251
53252     /**
53253      * An empty function by default, but provided so that you can perform a custom action before the dragged
53254      * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
53255      * @param {Ext.dd.DragDrop} target The drop target
53256      * @param {Event} e The event object
53257      * @param {String} id The id of the dragged element
53258      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
53259      */
53260     beforeDragOut: function(target, e, id){
53261         return true;
53262     },
53263
53264     // private
53265     onDragDrop: function(e, id){
53266         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
53267         if (this.beforeDragDrop(target, e, id) !== false) {
53268             if (target.isNotifyTarget) {
53269                 if (target.notifyDrop(this, e, this.dragData) !== false) { // valid drop?
53270                     this.onValidDrop(target, e, id);
53271                 } else {
53272                     this.onInvalidDrop(target, e, id);
53273                 }
53274             } else {
53275                 this.onValidDrop(target, e, id);
53276             }
53277
53278             if (this.afterDragDrop) {
53279                 /**
53280                  * An empty function by default, but provided so that you can perform a custom action
53281                  * after a valid drag drop has occurred by providing an implementation.
53282                  * @param {Ext.dd.DragDrop} target The drop target
53283                  * @param {Event} e The event object
53284                  * @param {String} id The id of the dropped element
53285                  * @method afterDragDrop
53286                  */
53287                 this.afterDragDrop(target, e, id);
53288             }
53289         }
53290         delete this.cachedTarget;
53291     },
53292
53293     /**
53294      * An empty function by default, but provided so that you can perform a custom action before the dragged
53295      * item is dropped onto the target and optionally cancel the onDragDrop.
53296      * @param {Ext.dd.DragDrop} target The drop target
53297      * @param {Event} e The event object
53298      * @param {String} id The id of the dragged element
53299      * @return {Boolean} isValid True if the drag drop event is valid, else false to cancel
53300      */
53301     beforeDragDrop: function(target, e, id){
53302         return true;
53303     },
53304
53305     // private
53306     onValidDrop: function(target, e, id){
53307         this.hideProxy();
53308         if(this.afterValidDrop){
53309             /**
53310              * An empty function by default, but provided so that you can perform a custom action
53311              * after a valid drop has occurred by providing an implementation.
53312              * @param {Object} target The target DD
53313              * @param {Event} e The event object
53314              * @param {String} id The id of the dropped element
53315              * @method afterInvalidDrop
53316              */
53317             this.afterValidDrop(target, e, id);
53318         }
53319     },
53320
53321     // private
53322     getRepairXY: function(e, data){
53323         return this.el.getXY();
53324     },
53325
53326     // private
53327     onInvalidDrop: function(target, e, id) {
53328         this.beforeInvalidDrop(target, e, id);
53329         if (this.cachedTarget) {
53330             if(this.cachedTarget.isNotifyTarget){
53331                 this.cachedTarget.notifyOut(this, e, this.dragData);
53332             }
53333             this.cacheTarget = null;
53334         }
53335         this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
53336
53337         if (this.afterInvalidDrop) {
53338             /**
53339              * An empty function by default, but provided so that you can perform a custom action
53340              * after an invalid drop has occurred by providing an implementation.
53341              * @param {Event} e The event object
53342              * @param {String} id The id of the dropped element
53343              * @method afterInvalidDrop
53344              */
53345             this.afterInvalidDrop(e, id);
53346         }
53347     },
53348
53349     // private
53350     afterRepair: function() {
53351         var me = this;
53352         if (Ext.enableFx) {
53353             me.el.highlight(me.repairHighlightColor);
53354         }
53355         me.dragging = false;
53356     },
53357
53358     /**
53359      * An empty function by default, but provided so that you can perform a custom action after an invalid
53360      * drop has occurred.
53361      * @param {Ext.dd.DragDrop} target The drop target
53362      * @param {Event} e The event object
53363      * @param {String} id The id of the dragged element
53364      * @return {Boolean} isValid True if the invalid drop should proceed, else false to cancel
53365      */
53366     beforeInvalidDrop: function(target, e, id) {
53367         return true;
53368     },
53369
53370     // private
53371     handleMouseDown: function(e) {
53372         if (this.dragging) {
53373             return;
53374         }
53375         var data = this.getDragData(e);
53376         if (data && this.onBeforeDrag(data, e) !== false) {
53377             this.dragData = data;
53378             this.proxy.stop();
53379             this.callParent(arguments);
53380         }
53381     },
53382
53383     /**
53384      * An empty function by default, but provided so that you can perform a custom action before the initial
53385      * drag event begins and optionally cancel it.
53386      * @param {Object} data An object containing arbitrary data to be shared with drop targets
53387      * @param {Event} e The event object
53388      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
53389      */
53390     onBeforeDrag: function(data, e){
53391         return true;
53392     },
53393
53394     /**
53395      * An empty function by default, but provided so that you can perform a custom action once the initial
53396      * drag event has begun.  The drag cannot be canceled from this function.
53397      * @param {Number} x The x position of the click on the dragged object
53398      * @param {Number} y The y position of the click on the dragged object
53399      * @method
53400      */
53401     onStartDrag: Ext.emptyFn,
53402
53403     // private override
53404     startDrag: function(x, y) {
53405         this.proxy.reset();
53406         this.dragging = true;
53407         this.proxy.update("");
53408         this.onInitDrag(x, y);
53409         this.proxy.show();
53410     },
53411
53412     // private
53413     onInitDrag: function(x, y) {
53414         var clone = this.el.dom.cloneNode(true);
53415         clone.id = Ext.id(); // prevent duplicate ids
53416         this.proxy.update(clone);
53417         this.onStartDrag(x, y);
53418         return true;
53419     },
53420
53421     /**
53422      * Returns the drag source's underlying {@link Ext.dd.StatusProxy}
53423      * @return {Ext.dd.StatusProxy} proxy The StatusProxy
53424      */
53425     getProxy: function() {
53426         return this.proxy;
53427     },
53428
53429     /**
53430      * Hides the drag source's {@link Ext.dd.StatusProxy}
53431      */
53432     hideProxy: function() {
53433         this.proxy.hide();
53434         this.proxy.reset(true);
53435         this.dragging = false;
53436     },
53437
53438     // private
53439     triggerCacheRefresh: function() {
53440         Ext.dd.DDM.refreshCache(this.groups);
53441     },
53442
53443     // private - override to prevent hiding
53444     b4EndDrag: function(e) {
53445     },
53446
53447     // private - override to prevent moving
53448     endDrag : function(e){
53449         this.onEndDrag(this.dragData, e);
53450     },
53451
53452     // private
53453     onEndDrag : function(data, e){
53454     },
53455
53456     // private - pin to cursor
53457     autoOffset : function(x, y) {
53458         this.setDelta(-12, -20);
53459     },
53460
53461     destroy: function(){
53462         this.callParent();
53463         Ext.destroy(this.proxy);
53464     }
53465 });
53466
53467 // private - DD implementation for Panels
53468 Ext.define('Ext.panel.DD', {
53469     extend: 'Ext.dd.DragSource',
53470     requires: ['Ext.panel.Proxy'],
53471
53472     constructor : function(panel, cfg){
53473         this.panel = panel;
53474         this.dragData = {panel: panel};
53475         this.proxy = Ext.create('Ext.panel.Proxy', panel, cfg);
53476
53477         this.callParent([panel.el, cfg]);
53478
53479         Ext.defer(function() {
53480             var header = panel.header,
53481                 el = panel.body;
53482
53483             if(header){
53484                 this.setHandleElId(header.id);
53485                 el = header.el;
53486             }
53487             el.setStyle('cursor', 'move');
53488             this.scroll = false;
53489         }, 200, this);
53490     },
53491
53492     showFrame: Ext.emptyFn,
53493     startDrag: Ext.emptyFn,
53494     b4StartDrag: function(x, y) {
53495         this.proxy.show();
53496     },
53497     b4MouseDown: function(e) {
53498         var x = e.getPageX(),
53499             y = e.getPageY();
53500         this.autoOffset(x, y);
53501     },
53502     onInitDrag : function(x, y){
53503         this.onStartDrag(x, y);
53504         return true;
53505     },
53506     createFrame : Ext.emptyFn,
53507     getDragEl : function(e){
53508         return this.proxy.ghost.el.dom;
53509     },
53510     endDrag : function(e){
53511         this.proxy.hide();
53512         this.panel.saveState();
53513     },
53514
53515     autoOffset : function(x, y) {
53516         x -= this.startPageX;
53517         y -= this.startPageY;
53518         this.setDelta(x, y);
53519     }
53520 });
53521
53522 /**
53523  * @class Ext.layout.component.Dock
53524  * @extends Ext.layout.component.AbstractDock
53525  * @private
53526  */
53527 Ext.define('Ext.layout.component.Dock', {
53528
53529     /* Begin Definitions */
53530
53531     alias: ['layout.dock'],
53532
53533     extend: 'Ext.layout.component.AbstractDock'
53534
53535     /* End Definitions */
53536
53537 });
53538 /**
53539  * @class Ext.panel.Panel
53540  * @extends Ext.panel.AbstractPanel
53541  * <p>Panel is a container that has specific functionality and structural components that make
53542  * it the perfect building block for application-oriented user interfaces.</p>
53543  * <p>Panels are, by virtue of their inheritance from {@link Ext.container.Container}, capable
53544  * of being configured with a {@link Ext.container.Container#layout layout}, and containing child Components.</p>
53545  * <p>When either specifying child {@link Ext.Component#items items} of a Panel, or dynamically {@link Ext.container.Container#add adding} Components
53546  * to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether
53547  * those child elements need to be sized using one of Ext&#39;s built-in <code><b>{@link Ext.container.Container#layout layout}</b></code> schemes. By
53548  * default, Panels use the {@link Ext.layout.container.Auto Auto} scheme. This simply renders
53549  * child components, appending them one after the other inside the Container, and <b>does not apply any sizing</b>
53550  * at all.</p>
53551  * {@img Ext.panel.Panel/panel.png Panel components}
53552  * <p>A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate
53553  * {@link #header}, {@link #footer} and {@link #body} sections (see {@link #frame} for additional
53554  * information).</p>
53555  * <p>Panel also provides built-in {@link #collapsible collapsible, expandable} and {@link #closable} behavior.
53556  * Panels can be easily dropped into any {@link Ext.container.Container Container} or layout, and the
53557  * layout and rendering pipeline is {@link Ext.container.Container#add completely managed by the framework}.</p>
53558  * <p><b>Note:</b> By default, the <code>{@link #closable close}</code> header tool <i>destroys</i> the Panel resulting in removal of the Panel
53559  * and the destruction of any descendant Components. This makes the Panel object, and all its descendants <b>unusable</b>. To enable the close
53560  * tool to simply <i>hide</i> a Panel for later re-use, configure the Panel with <b><code>{@link #closeAction closeAction: 'hide'}</code></b>.</p>
53561  * <p>Usually, Panels are used as constituents within an application, in which case, they would be used as child items of Containers,
53562  * and would themselves use Ext.Components as child {@link #items}. However to illustrate simply rendering a Panel into the document,
53563  * here&#39;s how to do it:<pre><code>
53564 Ext.create('Ext.panel.Panel', {
53565     title: 'Hello',
53566     width: 200,
53567     html: '&lt;p&gt;World!&lt;/p&gt;',
53568     renderTo: document.body
53569 });
53570 </code></pre></p>
53571  * <p>A more realistic scenario is a Panel created to house input fields which will not be rendered, but used as a constituent part of a Container:<pre><code>
53572 var filterPanel = Ext.create('Ext.panel.Panel', {
53573     bodyPadding: 5,  // Don&#39;t want content to crunch against the borders
53574     title: 'Filters',
53575     items: [{
53576         xtype: 'datefield',
53577         fieldLabel: 'Start date'
53578     }, {
53579         xtype: 'datefield',
53580         fieldLabel: 'End date'
53581     }]
53582 });
53583 </code></pre></p>
53584  * <p>Note that the Panel above is not configured to render into the document, nor is it configured with a size or position. In a real world scenario,
53585  * the Container into which the Panel is added will use a {@link #layout} to render, size and position its child Components.</p>
53586  * <p>Panels will often use specific {@link #layout}s to provide an application with shape and structure by containing and arranging child
53587  * Components: <pre><code>
53588 var resultsPanel = Ext.create('Ext.panel.Panel', {
53589     title: 'Results',
53590     width: 600,
53591     height: 400,
53592     renderTo: document.body,
53593     layout: {
53594         type: 'vbox',       // Arrange child items vertically
53595         align: 'stretch',    // Each takes up full width
53596         padding: 5
53597     },
53598     items: [{               // Results grid specified as a config object with an xtype of 'grid'
53599         xtype: 'grid',
53600         columns: [{header: 'Column One'}],            // One header just for show. There&#39;s no data,
53601         store: Ext.create('Ext.data.ArrayStore', {}), // A dummy empty data store
53602         flex: 1                                       // Use 1/3 of Container&#39;s height (hint to Box layout)
53603     }, {
53604         xtype: 'splitter'   // A splitter between the two child items
53605     }, {                    // Details Panel specified as a config object (no xtype defaults to 'panel').
53606         title: 'Details',
53607         bodyPadding: 5,
53608         items: [{
53609             fieldLabel: 'Data item',
53610             xtype: 'textfield'
53611         }], // An array of form fields
53612         flex: 2             // Use 2/3 of Container&#39;s height (hint to Box layout)
53613     }]
53614 });
53615 </code></pre>
53616  * The example illustrates one possible method of displaying search results. The Panel contains a grid with the resulting data arranged
53617  * in rows. Each selected row may be displayed in detail in the Panel below. The {@link Ext.layout.container.VBox vbox} layout is used
53618  * to arrange the two vertically. It is configured to stretch child items horizontally to full width. Child items may either be configured
53619  * with a numeric height, or with a <code>flex</code> value to distribute available space proportionately.</p>
53620  * <p>This Panel itself may be a child item of, for exaple, a {@link Ext.tab.Panel} which will size its child items to fit within its
53621  * content area.</p>
53622  * <p>Using these techniques, as long as the <b>layout</b> is chosen and configured correctly, an application may have any level of
53623  * nested containment, all dynamically sized according to configuration, the user&#39;s preference and available browser size.</p>
53624  * @constructor
53625  * @param {Object} config The config object
53626  * @xtype panel
53627  */
53628 Ext.define('Ext.panel.Panel', {
53629     extend: 'Ext.panel.AbstractPanel',
53630     requires: [
53631         'Ext.panel.Header',
53632         'Ext.fx.Anim',
53633         'Ext.util.KeyMap',
53634         'Ext.panel.DD',
53635         'Ext.XTemplate',
53636         'Ext.layout.component.Dock'
53637     ],
53638     alias: 'widget.panel',
53639     alternateClassName: 'Ext.Panel',
53640
53641     /**
53642      * @cfg {String} collapsedCls
53643      * A CSS class to add to the panel&#39;s element after it has been collapsed (defaults to
53644      * <code>'collapsed'</code>).
53645      */
53646     collapsedCls: 'collapsed',
53647
53648     /**
53649      * @cfg {Boolean} animCollapse
53650      * <code>true</code> to animate the transition when the panel is collapsed, <code>false</code> to skip the
53651      * animation (defaults to <code>true</code> if the {@link Ext.fx.Anim} class is available, otherwise <code>false</code>).
53652      * May also be specified as the animation duration in milliseconds.
53653      */
53654     animCollapse: Ext.enableFx,
53655
53656     /**
53657      * @cfg {Number} minButtonWidth
53658      * Minimum width of all footer toolbar buttons in pixels (defaults to <tt>75</tt>). If set, this will
53659      * be used as the default value for the <tt>{@link Ext.button.Button#minWidth}</tt> config of
53660      * each Button added to the <b>footer toolbar</b> via the {@link #fbar} or {@link #buttons} configurations.
53661      * It will be ignored for buttons that have a minWidth configured some other way, e.g. in their own config
53662      * object or via the {@link Ext.container.Container#config-defaults defaults} of their parent container.
53663      */
53664     minButtonWidth: 75,
53665
53666     /**
53667      * @cfg {Boolean} collapsed
53668      * <code>true</code> to render the panel collapsed, <code>false</code> to render it expanded (defaults to
53669      * <code>false</code>).
53670      */
53671     collapsed: false,
53672
53673     /**
53674      * @cfg {Boolean} collapseFirst
53675      * <code>true</code> to make sure the collapse/expand toggle button always renders first (to the left of)
53676      * any other tools in the panel&#39;s title bar, <code>false</code> to render it last (defaults to <code>true</code>).
53677      */
53678     collapseFirst: true,
53679
53680     /**
53681      * @cfg {Boolean} hideCollapseTool
53682      * <code>true</code> to hide the expand/collapse toggle button when <code>{@link #collapsible} == true</code>,
53683      * <code>false</code> to display it (defaults to <code>false</code>).
53684      */
53685     hideCollapseTool: false,
53686
53687     /**
53688      * @cfg {Boolean} titleCollapse
53689      * <code>true</code> to allow expanding and collapsing the panel (when <code>{@link #collapsible} = true</code>)
53690      * by clicking anywhere in the header bar, <code>false</code>) to allow it only by clicking to tool button
53691      * (defaults to <code>false</code>)).
53692      */
53693     titleCollapse: false,
53694
53695     /**
53696      * @cfg {String} collapseMode
53697      * <p><b>Important: this config is only effective for {@link #collapsible} Panels which are direct child items of a {@link Ext.layout.container.Border border layout}.</b></p>
53698      * <p>When <i>not</i> a direct child item of a {@link Ext.layout.container.Border border layout}, then the Panel&#39;s header remains visible, and the body is collapsed to zero dimensions.
53699      * If the Panel has no header, then a new header (orientated correctly depending on the {@link #collapseDirection}) will be inserted to show a the title and a re-expand tool.</p>
53700      * <p>When a child item of a {@link Ext.layout.container.Border border layout}, this config has two options:
53701      * <div class="mdetail-params"><ul>
53702      * <li><b><code>undefined/omitted</code></b><div class="sub-desc">When collapsed, a placeholder {@link Ext.panel.Header Header} is injected into the layout to represent the Panel
53703      * and to provide a UI with a Tool to allow the user to re-expand the Panel.</div></li>
53704      * <li><b><code>header</code></b> : <div class="sub-desc">The Panel collapses to leave its header visible as when not inside a {@link Ext.layout.container.Border border layout}.</div></li>
53705      * </ul></div></p>
53706      */
53707
53708     /**
53709      * @cfg {Mixed} placeholder
53710      * <p><b>Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a {@link Ext.layout.container.Border border layout}
53711      * when not using the <code>'header'</code> {@link #collapseMode}.</b></p>
53712      * <p><b>Optional.</b> A Component (or config object for a Component) to show in place of this Panel when this Panel is collapsed by a
53713      * {@link Ext.layout.container.Border border layout}. Defaults to a generated {@link Ext.panel.Header Header}
53714      * containing a {@link Ext.panel.Tool Tool} to re-expand the Panel.</p>
53715      */
53716
53717     /**
53718      * @cfg {Boolean} floatable
53719      * <p><b>Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a {@link Ext.layout.container.Border border layout}.</b></p>
53720      * <tt>true</tt> to allow clicking a collapsed Panel&#39;s {@link #placeholder} to display the Panel floated
53721      * above the layout, <tt>false</tt> to force the user to fully expand a collapsed region by
53722      * clicking the expand button to see it again (defaults to <tt>true</tt>).
53723      */
53724     floatable: true,
53725     
53726     /**
53727      * @cfg {Mixed} overlapHeader
53728      * True to overlap the header in a panel over the framing of the panel itself. This is needed when frame:true (and is done automatically for you). Otherwise it is undefined.
53729      * If you manually add rounded corners to a panel header which does not have frame:true, this will need to be set to true.
53730      */
53731     
53732     /**
53733      * @cfg {Boolean} collapsible
53734      * <p>True to make the panel collapsible and have an expand/collapse toggle Tool added into
53735      * the header tool button area. False to keep the panel sized either statically, or by an owning layout manager, with no toggle Tool (defaults to false).</p>
53736      * See {@link #collapseMode} and {@link #collapseDirection}
53737      */
53738     collapsible: false,
53739
53740     /**
53741      * @cfg {Boolean} collapseDirection
53742      * <p>The direction to collapse the Panel when the toggle button is clicked.</p>
53743      * <p>Defaults to the {@link #headerPosition}</p>
53744      * <p><b>Important: This config is <u>ignored</u> for {@link #collapsible} Panels which are direct child items of a {@link Ext.layout.container.Border border layout}.</b></p>
53745      * <p>Specify as <code>'top'</code>, <code>'bottom'</code>, <code>'left'</code> or <code>'right'</code>.</p>
53746      */
53747
53748     /**
53749      * @cfg {Boolean} closable
53750      * <p>True to display the 'close' tool button and allow the user to close the window, false to
53751      * hide the button and disallow closing the window (defaults to <code>false</code>).</p>
53752      * <p>By default, when close is requested by clicking the close button in the header, the {@link #close}
53753      * method will be called. This will <i>{@link Ext.Component#destroy destroy}</i> the Panel and its content
53754      * meaning that it may not be reused.</p>
53755      * <p>To make closing a Panel <i>hide</i> the Panel so that it may be reused, set
53756      * {@link #closeAction} to 'hide'.</p>
53757      */
53758     closable: false,
53759
53760     /**
53761      * @cfg {String} closeAction
53762      * <p>The action to take when the close header tool is clicked:
53763      * <div class="mdetail-params"><ul>
53764      * <li><b><code>'{@link #destroy}'</code></b> : <b>Default</b><div class="sub-desc">
53765      * {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy}
53766      * it and all descendant Components. The window will <b>not</b> be available to be
53767      * redisplayed via the {@link #show} method.
53768      * </div></li>
53769      * <li><b><code>'{@link #hide}'</code></b> : <div class="sub-desc">
53770      * {@link #hide} the window by setting visibility to hidden and applying negative offsets.
53771      * The window will be available to be redisplayed via the {@link #show} method.
53772      * </div></li>
53773      * </ul></div>
53774      * <p><b>Note:</b> This behavior has changed! setting *does* affect the {@link #close} method
53775      * which will invoke the approriate closeAction.
53776      */
53777     closeAction: 'destroy',
53778
53779     /**
53780      * @cfg {Object/Array} dockedItems
53781      * A component or series of components to be added as docked items to this panel.
53782      * The docked items can be docked to either the top, right, left or bottom of a panel.
53783      * This is typically used for things like toolbars or tab bars:
53784      * <pre><code>
53785 var panel = new Ext.panel.Panel({
53786     dockedItems: [{
53787         xtype: 'toolbar',
53788         dock: 'top',
53789         items: [{
53790             text: 'Docked to the top'
53791         }]
53792     }]
53793 });</pre></code>
53794      */
53795
53796     /**
53797       * @cfg {Boolean} preventHeader Prevent a Header from being created and shown. Defaults to false.
53798       */
53799     preventHeader: false,
53800
53801      /**
53802       * @cfg {String} headerPosition Specify as <code>'top'</code>, <code>'bottom'</code>, <code>'left'</code> or <code>'right'</code>. Defaults to <code>'top'</code>.
53803       */
53804     headerPosition: 'top',
53805
53806      /**
53807      * @cfg {Boolean} frame
53808      * True to apply a frame to the panel.
53809      */
53810     frame: false,
53811
53812     /**
53813      * @cfg {Boolean} frameHeader
53814      * True to apply a frame to the panel panels header (if 'frame' is true).
53815      */
53816     frameHeader: true,
53817
53818     /**
53819      * @cfg {Array} tools
53820      * An array of {@link Ext.panel.Tool} configs/instances to be added to the header tool area. The tools are stored as child
53821      * components of the header container. They can be accessed using {@link #down} and {#query}, as well as the other
53822      * component methods. The toggle tool is automatically created if {@link #collapsible} is set to true.
53823      * <p>Note that, apart from the toggle tool which is provided when a panel is collapsible, these
53824      * tools only provide the visual button. Any required functionality must be provided by adding
53825      * handlers that implement the necessary behavior.</p>
53826      * <p>Example usage:</p>
53827      * <pre><code>
53828 tools:[{
53829     type:'refresh',
53830     qtip: 'Refresh form Data',
53831     // hidden:true,
53832     handler: function(event, toolEl, panel){
53833         // refresh logic
53834     }
53835 },
53836 {
53837     type:'help',
53838     qtip: 'Get Help',
53839     handler: function(event, toolEl, panel){
53840         // show help here
53841     }
53842 }]
53843 </code></pre>
53844      */
53845
53846
53847     initComponent: function() {
53848         var me = this,
53849             cls;
53850
53851         me.addEvents(
53852         /**
53853          * @event titlechange
53854          * Fires after the Panel title has been set or changed.
53855          * @param {Ext.panel.Panel} p the Panel which has been resized.
53856          * @param {String} newTitle The new title.
53857          * @param {String} oldTitle The previous panel title.
53858          */
53859             'titlechange',
53860         /**
53861          * @event iconchange
53862          * Fires after the Panel iconCls has been set or changed.
53863          * @param {Ext.panel.Panel} p the Panel which has been resized.
53864          * @param {String} newIconCls The new iconCls.
53865          * @param {String} oldIconCls The previous panel iconCls.
53866          */
53867             'iconchange'
53868         );
53869
53870         if (me.unstyled) {
53871             me.setUI('plain');
53872         }
53873
53874         if (me.frame) {
53875             me.setUI('default-framed');
53876         }
53877
53878         me.callParent();
53879
53880         me.collapseDirection = me.collapseDirection || me.headerPosition || Ext.Component.DIRECTION_TOP;
53881
53882         // Backwards compatibility
53883         me.bridgeToolbars();
53884     },
53885
53886     setBorder: function(border) {
53887         // var me     = this,
53888         //     method = (border === false || border === 0) ? 'addClsWithUI' : 'removeClsWithUI';
53889         // 
53890         // me.callParent(arguments);
53891         // 
53892         // if (me.collapsed) {
53893         //     me[method](me.collapsedCls + '-noborder');
53894         // }
53895         // 
53896         // if (me.header) {
53897         //     me.header.setBorder(border);
53898         //     if (me.collapsed) {
53899         //         me.header[method](me.collapsedCls + '-noborder');
53900         //     }
53901         // }
53902         
53903         this.callParent(arguments);
53904     },
53905
53906     beforeDestroy: function() {
53907         Ext.destroy(
53908             this.ghostPanel,
53909             this.dd
53910         );
53911         this.callParent();
53912     },
53913
53914     initAria: function() {
53915         this.callParent();
53916         this.initHeaderAria();
53917     },
53918
53919     initHeaderAria: function() {
53920         var me = this,
53921             el = me.el,
53922             header = me.header;
53923         if (el && header) {
53924             el.dom.setAttribute('aria-labelledby', header.titleCmp.id);
53925         }
53926     },
53927
53928     getHeader: function() {
53929         return this.header;
53930     },
53931
53932     /**
53933      * Set a title for the panel&#39;s header. See {@link Ext.panel.Header#title}.
53934      * @param {String} newTitle
53935      */
53936     setTitle: function(newTitle) {
53937         var me = this,
53938         oldTitle = this.title;
53939
53940         me.title = newTitle;
53941         if (me.header) {
53942             me.header.setTitle(newTitle);
53943         } else {
53944             me.updateHeader();
53945         }
53946
53947         if (me.reExpander) {
53948             me.reExpander.setTitle(newTitle);
53949         }
53950         me.fireEvent('titlechange', me, newTitle, oldTitle);
53951     },
53952
53953     /**
53954      * Set the iconCls for the panel&#39;s header. See {@link Ext.panel.Header#iconCls}.
53955      * @param {String} newIconCls
53956      */
53957     setIconCls: function(newIconCls) {
53958         var me = this,
53959             oldIconCls = me.iconCls;
53960
53961         me.iconCls = newIconCls;
53962         var header = me.header;
53963         if (header) {
53964             header.setIconCls(newIconCls);
53965         }
53966         me.fireEvent('iconchange', me, newIconCls, oldIconCls);
53967     },
53968
53969     bridgeToolbars: function() {
53970         var me = this,
53971             fbar,
53972             fbarDefaults,
53973             minButtonWidth = me.minButtonWidth;
53974
53975         function initToolbar (toolbar, pos) {
53976             if (Ext.isArray(toolbar)) {
53977                 toolbar = {
53978                     xtype: 'toolbar',
53979                     items: toolbar
53980                 };
53981             }
53982             else if (!toolbar.xtype) {
53983                 toolbar.xtype = 'toolbar';
53984             }
53985             toolbar.dock = pos;
53986             if (pos == 'left' || pos == 'right') {
53987                 toolbar.vertical = true;
53988             }
53989             return toolbar;
53990         }
53991
53992         // Backwards compatibility
53993
53994         /**
53995          * @cfg {Object/Array} tbar
53996
53997 Convenience method. Short for 'Top Bar'.
53998
53999     tbar: [
54000       { xtype: 'button', text: 'Button 1' }
54001     ]
54002
54003 is equivalent to
54004
54005     dockedItems: [{
54006         xtype: 'toolbar',
54007         dock: 'top',
54008         items: [
54009             { xtype: 'button', text: 'Button 1' }
54010         ]
54011     }]
54012
54013          * @markdown
54014          */
54015         if (me.tbar) {
54016             me.addDocked(initToolbar(me.tbar, 'top'));
54017             me.tbar = null;
54018         }
54019
54020         /**
54021          * @cfg {Object/Array} bbar
54022
54023 Convenience method. Short for 'Bottom Bar'.
54024
54025     bbar: [
54026       { xtype: 'button', text: 'Button 1' }
54027     ]
54028
54029 is equivalent to
54030
54031     dockedItems: [{
54032         xtype: 'toolbar',
54033         dock: 'bottom',
54034         items: [
54035             { xtype: 'button', text: 'Button 1' }
54036         ]
54037     }]
54038
54039          * @markdown
54040          */
54041         if (me.bbar) {
54042             me.addDocked(initToolbar(me.bbar, 'bottom'));
54043             me.bbar = null;
54044         }
54045
54046         /**
54047          * @cfg {Object/Array} buttons
54048
54049 Convenience method used for adding buttons docked to the bottom right of the panel. This is a
54050 synonym for the {@link #fbar} config.
54051
54052     buttons: [
54053       { text: 'Button 1' }
54054     ]
54055
54056 is equivalent to
54057
54058     dockedItems: [{
54059         xtype: 'toolbar',
54060         dock: 'bottom',
54061         defaults: {minWidth: {@link #minButtonWidth}},
54062         items: [
54063             { xtype: 'component', flex: 1 },
54064             { xtype: 'button', text: 'Button 1' }
54065         ]
54066     }]
54067
54068 The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
54069 each of the buttons in the buttons toolbar.
54070
54071          * @markdown
54072          */
54073         if (me.buttons) {
54074             me.fbar = me.buttons;
54075             me.buttons = null;
54076         }
54077
54078         /**
54079          * @cfg {Object/Array} fbar
54080
54081 Convenience method used for adding items to the bottom right of the panel. Short for Footer Bar.
54082
54083     fbar: [
54084       { type: 'button', text: 'Button 1' }
54085     ]
54086
54087 is equivalent to
54088
54089     dockedItems: [{
54090         xtype: 'toolbar',
54091         dock: 'bottom',
54092         defaults: {minWidth: {@link #minButtonWidth}},
54093         items: [
54094             { xtype: 'component', flex: 1 },
54095             { xtype: 'button', text: 'Button 1' }
54096         ]
54097     }]
54098
54099 The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
54100 each of the buttons in the fbar.
54101
54102          * @markdown
54103          */
54104         if (me.fbar) {
54105             fbar = initToolbar(me.fbar, 'bottom');
54106             fbar.ui = 'footer';
54107
54108             // Apply the minButtonWidth config to buttons in the toolbar
54109             if (minButtonWidth) {
54110                 fbarDefaults = fbar.defaults;
54111                 fbar.defaults = function(config) {
54112                     var defaults = fbarDefaults || {};
54113                     if ((!config.xtype || config.xtype === 'button' || (config.isComponent && config.isXType('button'))) &&
54114                             !('minWidth' in defaults)) {
54115                         defaults = Ext.apply({minWidth: minButtonWidth}, defaults);
54116                     }
54117                     return defaults;
54118                 };
54119             }
54120
54121             fbar = me.addDocked(fbar)[0];
54122             fbar.insert(0, {
54123                 flex: 1,
54124                 xtype: 'component',
54125                 focusable: false
54126             });
54127             me.fbar = null;
54128         }
54129
54130         /**
54131          * @cfg {Object/Array} lbar
54132          *
54133          * Convenience method. Short for 'Left Bar' (left-docked, vertical toolbar).
54134          *
54135          *    lbar: [
54136          *      { xtype: 'button', text: 'Button 1' }
54137          *    ]
54138          *
54139          * is equivalent to
54140          *
54141          *    dockedItems: [{
54142          *        xtype: 'toolbar',
54143          *        dock: 'left',
54144          *        items: [
54145          *            { xtype: 'button', text: 'Button 1' }
54146          *        ]
54147          *    }]
54148          *
54149          * @markdown
54150          */
54151         if (me.lbar) {
54152             me.addDocked(initToolbar(me.lbar, 'left'));
54153             me.lbar = null;
54154         }
54155
54156         /**
54157          * @cfg {Object/Array} rbar
54158          *
54159          * Convenience method. Short for 'Right Bar' (right-docked, vertical toolbar).
54160          *
54161          *    rbar: [
54162          *      { xtype: 'button', text: 'Button 1' }
54163          *    ]
54164          *
54165          * is equivalent to
54166          *
54167          *    dockedItems: [{
54168          *        xtype: 'toolbar',
54169          *        dock: 'right',
54170          *        items: [
54171          *            { xtype: 'button', text: 'Button 1' }
54172          *        ]
54173          *    }]
54174          *
54175          * @markdown
54176          */
54177         if (me.rbar) {
54178             me.addDocked(initToolbar(me.rbar, 'right'));
54179             me.rbar = null;
54180         }
54181     },
54182
54183     /**
54184      * @private
54185      * Tools are a Panel-specific capabilty.
54186      * Panel uses initTools. Subclasses may contribute tools by implementing addTools.
54187      */
54188     initTools: function() {
54189         var me = this;
54190
54191         me.tools = me.tools || [];
54192
54193         // Add a collapse tool unless configured to not show a collapse tool
54194         // or to not even show a header.
54195         if (me.collapsible && !(me.hideCollapseTool || me.header === false)) {
54196             me.collapseDirection = me.collapseDirection || me.headerPosition || 'top';
54197             me.collapseTool = me.expandTool = me.createComponent({
54198                 xtype: 'tool',
54199                 type: 'collapse-' + me.collapseDirection,
54200                 expandType: me.getOppositeDirection(me.collapseDirection),
54201                 handler: me.toggleCollapse,
54202                 scope: me
54203             });
54204
54205             // Prepend collapse tool is configured to do so.
54206             if (me.collapseFirst) {
54207                 me.tools.unshift(me.collapseTool);
54208             }
54209         }
54210
54211         // Add subclass-specific tools.
54212         me.addTools();
54213
54214         // Make Panel closable.
54215         if (me.closable) {
54216             me.addClsWithUI('closable');
54217             me.addTool({
54218                 type: 'close',
54219                 handler: Ext.Function.bind(me.close, this, [])
54220             });
54221         }
54222
54223         // Append collapse tool if needed.
54224         if (me.collapseTool && !me.collapseFirst) {
54225             me.tools.push(me.collapseTool);
54226         }
54227     },
54228
54229     /**
54230      * @private
54231      * Template method to be implemented in subclasses to add their tools after the collapsible tool.
54232      */
54233     addTools: Ext.emptyFn,
54234
54235     /**
54236      * <p>Closes the Panel. By default, this method, removes it from the DOM, {@link Ext.Component#destroy destroy}s
54237      * the Panel object and all its descendant Components. The {@link #beforeclose beforeclose}
54238      * event is fired before the close happens and will cancel the close action if it returns false.<p>
54239      * <p><b>Note:</b> This method is not affected by the {@link #closeAction} setting which
54240      * only affects the action triggered when clicking the {@link #closable 'close' tool in the header}.
54241      * To hide the Panel without destroying it, call {@link #hide}.</p>
54242      */
54243     close: function() {
54244         if (this.fireEvent('beforeclose', this) !== false) {
54245             this.doClose();
54246         }
54247     },
54248
54249     // private
54250     doClose: function() {
54251         this.fireEvent('close', this);
54252         this[this.closeAction]();
54253     },
54254
54255     onRender: function(ct, position) {
54256         var me = this,
54257             topContainer;
54258
54259         // Add class-specific header tools.
54260         // Panel adds collapsible and closable.
54261         me.initTools();
54262
54263         // Dock the header/title
54264         me.updateHeader();
54265
54266         // If initially collapsed, collapsed flag must indicate true current state at this point.
54267         // Do collapse after the first time the Panel's structure has been laid out.
54268         if (me.collapsed) {
54269             me.collapsed = false;
54270             topContainer = me.findLayoutController();
54271             if (!me.hidden && topContainer) {
54272                 topContainer.on({
54273                     afterlayout: function() {
54274                         me.collapse(null, false, true);
54275                     },
54276                     single: true
54277                 });
54278             } else {
54279                 me.afterComponentLayout = function() {
54280                     delete me.afterComponentLayout;
54281                     Ext.getClass(me).prototype.afterComponentLayout.apply(me, arguments);
54282                     me.collapse(null, false, true);
54283                 };
54284             }
54285         }
54286
54287         // Call to super after adding the header, to prevent an unnecessary re-layout
54288         me.callParent(arguments);
54289     },
54290
54291     /**
54292      * Create, hide, or show the header component as appropriate based on the current config.
54293      * @private
54294      * @param {Boolean} force True to force the the header to be created
54295      */
54296     updateHeader: function(force) {
54297         var me = this,
54298             header = me.header,
54299             title = me.title,
54300             tools = me.tools;
54301
54302         if (!me.preventHeader && (force || title || (tools && tools.length))) {
54303             if (!header) {
54304                 header = me.header = Ext.create('Ext.panel.Header', {
54305                     title       : title,
54306                     orientation : (me.headerPosition == 'left' || me.headerPosition == 'right') ? 'vertical' : 'horizontal',
54307                     dock        : me.headerPosition || 'top',
54308                     textCls     : me.headerTextCls,
54309                     iconCls     : me.iconCls,
54310                     baseCls     : me.baseCls + '-header',
54311                     tools       : tools,
54312                     ui          : me.ui,
54313                     indicateDrag: me.draggable,
54314                     border      : me.border,
54315                     frame       : me.frame && me.frameHeader,
54316                     ignoreParentFrame : me.frame || me.overlapHeader,
54317                     ignoreBorderManagement: me.frame || me.ignoreHeaderBorderManagement,
54318                     listeners   : me.collapsible && me.titleCollapse ? {
54319                         click: me.toggleCollapse,
54320                         scope: me
54321                     } : null
54322                 });
54323                 me.addDocked(header, 0);
54324
54325                 // Reference the Header's tool array.
54326                 // Header injects named references.
54327                 me.tools = header.tools;
54328             }
54329             header.show();
54330             me.initHeaderAria();
54331         } else if (header) {
54332             header.hide();
54333         }
54334     },
54335
54336     // inherit docs
54337     setUI: function(ui) {
54338         var me = this;
54339
54340         me.callParent(arguments);
54341
54342         if (me.header) {
54343             me.header.setUI(ui);
54344         }
54345     },
54346
54347     // private
54348     getContentTarget: function() {
54349         return this.body;
54350     },
54351
54352     getTargetEl: function() {
54353         return this.body || this.frameBody || this.el;
54354     },
54355
54356     addTool: function(tool) {
54357         this.tools.push(tool);
54358         var header = this.header;
54359         if (header) {
54360             header.addTool(tool);
54361         }
54362         this.updateHeader();
54363     },
54364
54365     getOppositeDirection: function(d) {
54366         var c = Ext.Component;
54367         switch (d) {
54368             case c.DIRECTION_TOP:
54369                 return c.DIRECTION_BOTTOM;
54370             case c.DIRECTION_RIGHT:
54371                 return c.DIRECTION_LEFT;
54372             case c.DIRECTION_BOTTOM:
54373                 return c.DIRECTION_TOP;
54374             case c.DIRECTION_LEFT:
54375                 return c.DIRECTION_RIGHT;
54376         }
54377     },
54378
54379     /**
54380      * Collapses the panel body so that the body becomes hidden. Docked Components parallel to the
54381      * border towards which the collapse takes place will remain visible.  Fires the {@link #beforecollapse} event which will
54382      * cancel the collapse action if it returns false.
54383      * @param {Number} direction. The direction to collapse towards. Must be one of<ul>
54384      * <li>Ext.Component.DIRECTION_TOP</li>
54385      * <li>Ext.Component.DIRECTION_RIGHT</li>
54386      * <li>Ext.Component.DIRECTION_BOTTOM</li>
54387      * <li>Ext.Component.DIRECTION_LEFT</li></ul>
54388      * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
54389      * {@link #animCollapse} panel config)
54390      * @return {Ext.panel.Panel} this
54391      */
54392     collapse: function(direction, animate, /* private - passed if called at render time */ internal) {
54393         var me = this,
54394             c = Ext.Component,
54395             height = me.getHeight(),
54396             width = me.getWidth(),
54397             frameInfo,
54398             newSize = 0,
54399             dockedItems = me.dockedItems.items,
54400             dockedItemCount = dockedItems.length,
54401             i = 0,
54402             comp,
54403             pos,
54404             anim = {
54405                 from: {
54406                     height: height,
54407                     width: width
54408                 },
54409                 to: {
54410                     height: height,
54411                     width: width
54412                 },
54413                 listeners: {
54414                     afteranimate: me.afterCollapse,
54415                     scope: me
54416                 },
54417                 duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration)
54418             },
54419             reExpander,
54420             reExpanderOrientation,
54421             reExpanderDock,
54422             getDimension,
54423             setDimension,
54424             collapseDimension;
54425
54426         if (!direction) {
54427             direction = me.collapseDirection;
54428         }
54429
54430         // If internal (Called because of initial collapsed state), then no animation, and no events.
54431         if (internal) {
54432             animate = false;
54433         } else if (me.collapsed || me.fireEvent('beforecollapse', me, direction, animate) === false) {
54434             return false;
54435         }
54436
54437         reExpanderDock = direction;
54438         me.expandDirection = me.getOppositeDirection(direction);
54439
54440         // Track docked items which we hide during collapsed state
54441         me.hiddenDocked = [];
54442
54443         switch (direction) {
54444             case c.DIRECTION_TOP:
54445             case c.DIRECTION_BOTTOM:
54446                 me.expandedSize = me.getHeight();
54447                 reExpanderOrientation = 'horizontal';
54448                 collapseDimension = 'height';
54449                 getDimension = 'getHeight';
54450                 setDimension = 'setHeight';
54451
54452                 // Collect the height of the visible header.
54453                 // Hide all docked items except the header.
54454                 // Hide *ALL* docked items if we're going to end up hiding the whole Panel anyway
54455                 for (; i < dockedItemCount; i++) {
54456                     comp = dockedItems[i];
54457                     if (comp.isVisible()) {
54458                         if (comp.isHeader && (!comp.dock || comp.dock == 'top' || comp.dock == 'bottom')) {
54459                             reExpander = comp;
54460                         } else {
54461                             me.hiddenDocked.push(comp);
54462                         }
54463                     }
54464                 }
54465
54466                 if (direction == Ext.Component.DIRECTION_BOTTOM) {
54467                     pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
54468                     anim.from.top = pos;
54469                 }
54470                 break;
54471
54472             case c.DIRECTION_LEFT:
54473             case c.DIRECTION_RIGHT:
54474                 me.expandedSize = me.getWidth();
54475                 reExpanderOrientation = 'vertical';
54476                 collapseDimension = 'width';
54477                 getDimension = 'getWidth';
54478                 setDimension = 'setWidth';
54479
54480                 // Collect the height of the visible header.
54481                 // Hide all docked items except the header.
54482                 // Hide *ALL* docked items if we're going to end up hiding the whole Panel anyway
54483                 for (; i < dockedItemCount; i++) {
54484                     comp = dockedItems[i];
54485                     if (comp.isVisible()) {
54486                         if (comp.isHeader && (comp.dock == 'left' || comp.dock == 'right')) {
54487                             reExpander = comp;
54488                         } else {
54489                             me.hiddenDocked.push(comp);
54490                         }
54491                     }
54492                 }
54493
54494                 if (direction == Ext.Component.DIRECTION_RIGHT) {
54495                     pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
54496                     anim.from.left = pos;
54497                 }
54498                 break;
54499
54500             default:
54501                 throw('Panel collapse must be passed a valid Component collapse direction');
54502         }
54503
54504         // No scrollbars when we shrink this Panel
54505         // And no laying out of any children... we're effectively *hiding* the body
54506         me.setAutoScroll(false);
54507         me.suspendLayout = true;
54508         me.body.setVisibilityMode(Ext.core.Element.DISPLAY);
54509
54510         // Disable toggle tool during animated collapse
54511         if (animate && me.collapseTool) {
54512             me.collapseTool.disable();
54513         }
54514
54515         // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken.
54516         me.addClsWithUI(me.collapsedCls);
54517         // if (me.border === false) {
54518         //     me.addClsWithUI(me.collapsedCls + '-noborder');
54519         // }
54520
54521         // We found a header: Measure it to find the collapse-to size.
54522         if (reExpander) {
54523             //we must add the collapsed cls to the header and then remove to get the proper height
54524             reExpander.addClsWithUI(me.collapsedCls);
54525             reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
54526             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
54527                 reExpander.addClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
54528             }
54529
54530             frameInfo = reExpander.getFrameInfo();
54531                         
54532             //get the size
54533             newSize = reExpander[getDimension]() + (frameInfo ? frameInfo[direction] : 0);
54534
54535             //and remove
54536             reExpander.removeClsWithUI(me.collapsedCls);
54537             reExpander.removeClsWithUI(me.collapsedCls + '-' + reExpander.dock);              
54538             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
54539                 reExpander.removeClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
54540             }
54541         }
54542         // No header: Render and insert a temporary one, and then measure it.
54543         else {
54544             reExpander = {
54545                 hideMode: 'offsets',
54546                 temporary: true,
54547                 title: me.title,
54548                 orientation: reExpanderOrientation,
54549                 dock: reExpanderDock,
54550                 textCls: me.headerTextCls,
54551                 iconCls: me.iconCls,
54552                 baseCls: me.baseCls + '-header',
54553                 ui: me.ui,
54554                 frame: me.frame && me.frameHeader,
54555                 ignoreParentFrame: me.frame || me.overlapHeader,
54556                 indicateDrag: me.draggable,
54557                 cls: me.baseCls + '-collapsed-placeholder ' + ' ' + Ext.baseCSSPrefix + 'docked ' + me.baseCls + '-' + me.ui + '-collapsed',
54558                 renderTo: me.el
54559             };
54560             if (!me.hideCollapseTool) {
54561                 reExpander[(reExpander.orientation == 'horizontal') ? 'tools' : 'items'] = [{
54562                     xtype: 'tool',
54563                     type: 'expand-' + me.expandDirection,
54564                     handler: me.toggleCollapse,
54565                     scope: me
54566                 }];
54567             }
54568
54569             // Capture the size of the re-expander.
54570             // For vertical headers in IE6 and IE7, this will be sized by a CSS rule in _panel.scss
54571             reExpander = me.reExpander = Ext.create('Ext.panel.Header', reExpander);
54572             newSize = reExpander[getDimension]() + ((reExpander.frame) ? reExpander.frameSize[direction] : 0);
54573             reExpander.hide();
54574
54575             // Insert the new docked item
54576             me.insertDocked(0, reExpander);
54577         }
54578
54579         me.reExpander = reExpander;
54580         me.reExpander.addClsWithUI(me.collapsedCls);
54581         me.reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
54582         if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
54583             me.reExpander.addClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
54584         }
54585
54586         // If collapsing right or down, we'll be also animating the left or top.
54587         if (direction == Ext.Component.DIRECTION_RIGHT) {
54588             anim.to.left = pos + (width - newSize);
54589         } else if (direction == Ext.Component.DIRECTION_BOTTOM) {
54590             anim.to.top = pos + (height - newSize);
54591         }
54592
54593         // Animate to the new size
54594         anim.to[collapseDimension] = newSize;
54595
54596         // Remove any flex config before we attempt to collapse.
54597         me.savedFlex = me.flex;
54598         me.savedMinWidth = me.minWidth;
54599         me.savedMinHeight = me.minHeight;
54600         me.minWidth = 0;
54601         me.minHeight = 0;
54602         delete me.flex;
54603
54604         if (animate) {
54605             me.animate(anim);
54606         } else {
54607             // EXTJSIV-1937 (would like to use setCalculateSize)
54608             // save width/height here, expand puts them back
54609             me.uncollapsedSize = { width: me.width, height: me.height };
54610
54611             me.setSize(anim.to.width, anim.to.height);
54612             if (Ext.isDefined(anim.to.left) || Ext.isDefined(anim.to.top)) {
54613                 me.setPosition(anim.to.left, anim.to.top);
54614             }
54615             me.afterCollapse(false, internal);
54616         }
54617         return me;
54618     },
54619
54620     afterCollapse: function(animated, internal) {
54621         var me = this,
54622             i = 0,
54623             l = me.hiddenDocked.length;
54624
54625         me.minWidth = me.savedMinWidth;
54626         me.minHeight = me.savedMinHeight;
54627
54628         me.body.hide();
54629         for (; i < l; i++) {
54630             me.hiddenDocked[i].hide();
54631         }
54632         if (me.reExpander) {
54633             me.reExpander.updateFrame();
54634             me.reExpander.show();
54635         }
54636         me.collapsed = true;
54637
54638         if (!internal) {
54639             me.doComponentLayout();
54640         }
54641
54642         if (me.resizer) {
54643             me.resizer.disable();
54644         }
54645
54646         // If me Panel was configured with a collapse tool in its header, flip it's type
54647         if (me.collapseTool) {
54648             me.collapseTool.setType('expand-' + me.expandDirection);
54649         }
54650         if (!internal) {
54651             me.fireEvent('collapse', me);
54652         }
54653
54654         // Re-enable the toggle tool after an animated collapse
54655         if (animated && me.collapseTool) {
54656             me.collapseTool.enable();
54657         }
54658     },
54659
54660     /**
54661      * Expands the panel body so that it becomes visible.  Fires the {@link #beforeexpand} event which will
54662      * cancel the expand action if it returns false.
54663      * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
54664      * {@link #animCollapse} panel config)
54665      * @return {Ext.panel.Panel} this
54666      */
54667     expand: function(animate) {
54668         if (!this.collapsed || this.fireEvent('beforeexpand', this, animate) === false) {
54669             return false;
54670         }
54671
54672         // EXTJSIV-1937 (would like to use setCalculateSize)
54673         if (this.uncollapsedSize) {
54674             Ext.Object.each(this.uncollapsedSize, function (name, value) {
54675                 if (Ext.isDefined(value)) {
54676                     this[name] = value;
54677                 } else {
54678                     delete this[name];
54679                 }
54680             }, this);
54681             delete this.uncollapsedSize;
54682         }
54683
54684         var me = this,
54685             i = 0,
54686             l = me.hiddenDocked.length,
54687             direction = me.expandDirection,
54688             height = me.getHeight(),
54689             width = me.getWidth(),
54690             pos, anim, satisfyJSLint;
54691
54692         // Disable toggle tool during animated expand
54693         if (animate && me.collapseTool) {
54694             me.collapseTool.disable();
54695         }
54696
54697         // Show any docked items that we hid on collapse
54698         // And hide the injected reExpander Header
54699         for (; i < l; i++) {
54700             me.hiddenDocked[i].hidden = false;
54701             me.hiddenDocked[i].el.show();
54702         }
54703         if (me.reExpander) {
54704             if (me.reExpander.temporary) {
54705                 me.reExpander.hide();
54706             } else {
54707                 me.reExpander.removeClsWithUI(me.collapsedCls);
54708                 me.reExpander.removeClsWithUI(me.collapsedCls + '-' + me.reExpander.dock);
54709                 if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
54710                     me.reExpander.removeClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
54711                 }
54712                 me.reExpander.updateFrame();
54713             }
54714         }
54715
54716         // If me Panel was configured with a collapse tool in its header, flip it's type
54717         if (me.collapseTool) {
54718             me.collapseTool.setType('collapse-' + me.collapseDirection);
54719         }
54720
54721         // Unset the flag before the potential call to calculateChildBox to calculate our newly flexed size
54722         me.collapsed = false;
54723
54724         // Collapsed means body element was hidden
54725         me.body.show();
54726
54727         // Remove any collapsed styling before any animation begins
54728         me.removeClsWithUI(me.collapsedCls);
54729         // if (me.border === false) {
54730         //     me.removeClsWithUI(me.collapsedCls + '-noborder');
54731         // }
54732
54733         anim = {
54734             to: {
54735             },
54736             from: {
54737                 height: height,
54738                 width: width
54739             },
54740             listeners: {
54741                 afteranimate: me.afterExpand,
54742                 scope: me
54743             }
54744         };
54745
54746         if ((direction == Ext.Component.DIRECTION_TOP) || (direction == Ext.Component.DIRECTION_BOTTOM)) {
54747
54748             // If autoHeight, measure the height now we have shown the body element.
54749             if (me.autoHeight) {
54750                 me.setCalculatedSize(me.width, null);
54751                 anim.to.height = me.getHeight();
54752
54753                 // Must size back down to collapsed for the animation.
54754                 me.setCalculatedSize(me.width, anim.from.height);
54755             }
54756             // If we were flexed, then we can't just restore to the saved size.
54757             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
54758             else if (me.savedFlex) {
54759                 me.flex = me.savedFlex;
54760                 anim.to.height = me.ownerCt.layout.calculateChildBox(me).height;
54761                 delete me.flex;
54762             }
54763             // Else, restore to saved height
54764             else {
54765                 anim.to.height = me.expandedSize;
54766             }
54767
54768             // top needs animating upwards
54769             if (direction == Ext.Component.DIRECTION_TOP) {
54770                 pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
54771                 anim.from.top = pos;
54772                 anim.to.top = pos - (anim.to.height - height);
54773             }
54774         } else if ((direction == Ext.Component.DIRECTION_LEFT) || (direction == Ext.Component.DIRECTION_RIGHT)) {
54775
54776             // If autoWidth, measure the width now we have shown the body element.
54777             if (me.autoWidth) {
54778                 me.setCalculatedSize(null, me.height);
54779                 anim.to.width = me.getWidth();
54780
54781                 // Must size back down to collapsed for the animation.
54782                 me.setCalculatedSize(anim.from.width, me.height);
54783             }
54784             // If we were flexed, then we can't just restore to the saved size.
54785             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
54786             else if (me.savedFlex) {
54787                 me.flex = me.savedFlex;
54788                 anim.to.width = me.ownerCt.layout.calculateChildBox(me).width;
54789                 delete me.flex;
54790             }
54791             // Else, restore to saved width
54792             else {
54793                 anim.to.width = me.expandedSize;
54794             }
54795
54796             // left needs animating leftwards
54797             if (direction == Ext.Component.DIRECTION_LEFT) {
54798                 pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
54799                 anim.from.left = pos;
54800                 anim.to.left = pos - (anim.to.width - width);
54801             }
54802         }
54803
54804         if (animate) {
54805             me.animate(anim);
54806         } else {
54807             me.setSize(anim.to.width, anim.to.height);
54808             if (anim.to.x) {
54809                 me.setLeft(anim.to.x);
54810             }
54811             if (anim.to.y) {
54812                 me.setTop(anim.to.y);
54813             }
54814             me.afterExpand(false);
54815         }
54816
54817         return me;
54818     },
54819
54820     afterExpand: function(animated) {
54821         var me = this;
54822         me.setAutoScroll(me.initialConfig.autoScroll);
54823
54824         // Restored to a calculated flex. Delete the set width and height properties so that flex works from now on.
54825         if (me.savedFlex) {
54826             me.flex = me.savedFlex;
54827             delete me.savedFlex;
54828             delete me.width;
54829             delete me.height;
54830         }
54831
54832         // Reinstate layout out after Panel has re-expanded
54833         delete me.suspendLayout;
54834         if (animated && me.ownerCt) {
54835             me.ownerCt.doLayout();
54836         }
54837
54838         if (me.resizer) {
54839             me.resizer.enable();
54840         }
54841
54842         me.fireEvent('expand', me);
54843
54844         // Re-enable the toggle tool after an animated expand
54845         if (animated && me.collapseTool) {
54846             me.collapseTool.enable();
54847         }
54848     },
54849
54850     /**
54851      * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
54852      * @return {Ext.panel.Panel} this
54853      */
54854     toggleCollapse: function() {
54855         if (this.collapsed) {
54856             this.expand(this.animCollapse);
54857         } else {
54858             this.collapse(this.collapseDirection, this.animCollapse);
54859         }
54860         return this;
54861     },
54862
54863     // private
54864     getKeyMap : function(){
54865         if(!this.keyMap){
54866             this.keyMap = Ext.create('Ext.util.KeyMap', this.el, this.keys);
54867         }
54868         return this.keyMap;
54869     },
54870
54871     // private
54872     initDraggable : function(){
54873         /**
54874          * <p>If this Panel is configured {@link #draggable}, this property will contain
54875          * an instance of {@link Ext.dd.DragSource} which handles dragging the Panel.</p>
54876          * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
54877          * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
54878          * @type Ext.dd.DragSource.
54879          * @property dd
54880          */
54881         this.dd = Ext.create('Ext.panel.DD', this, Ext.isBoolean(this.draggable) ? null : this.draggable);
54882     },
54883
54884     // private - helper function for ghost
54885     ghostTools : function() {
54886         var tools = [],
54887             origTools = this.initialConfig.tools;
54888
54889         if (origTools) {
54890             Ext.each(origTools, function(tool) {
54891                 // Some tools can be full components, and copying them into the ghost
54892                 // actually removes them from the owning panel. You could also potentially
54893                 // end up with duplicate DOM ids as well. To avoid any issues we just make
54894                 // a simple bare-minimum clone of each tool for ghosting purposes.
54895                 tools.push({
54896                     type: tool.type
54897                 });
54898             });
54899         }
54900         else {
54901             tools = [{
54902                 type: 'placeholder'
54903             }];
54904         }
54905         return tools;
54906     },
54907
54908     // private - used for dragging
54909     ghost: function(cls) {
54910         var me = this,
54911             ghostPanel = me.ghostPanel,
54912             box = me.getBox();
54913
54914         if (!ghostPanel) {
54915             ghostPanel = Ext.create('Ext.panel.Panel', {
54916                 renderTo: document.body,
54917                 floating: {
54918                     shadow: false
54919                 },
54920                 frame: Ext.supports.CSS3BorderRadius ? me.frame : false,
54921                 title: me.title,
54922                 overlapHeader: me.overlapHeader,
54923                 headerPosition: me.headerPosition,
54924                 width: me.getWidth(),
54925                 height: me.getHeight(),
54926                 iconCls: me.iconCls,
54927                 baseCls: me.baseCls,
54928                 tools: me.ghostTools(),
54929                 cls: me.baseCls + '-ghost ' + (cls ||'')
54930             });
54931             me.ghostPanel = ghostPanel;
54932         }
54933         ghostPanel.floatParent = me.floatParent;
54934         if (me.floating) {
54935             ghostPanel.setZIndex(Ext.Number.from(me.el.getStyle('zIndex'), 0));
54936         } else {
54937             ghostPanel.toFront();
54938         }
54939         ghostPanel.el.show();
54940         ghostPanel.setPosition(box.x, box.y);
54941         ghostPanel.setSize(box.width, box.height);
54942         me.el.hide();
54943         if (me.floatingItems) {
54944             me.floatingItems.hide();
54945         }
54946         return ghostPanel;
54947     },
54948
54949     // private
54950     unghost: function(show, matchPosition) {
54951         var me = this;
54952         if (!me.ghostPanel) {
54953             return;
54954         }
54955         if (show !== false) {
54956             me.el.show();
54957             if (matchPosition !== false) {
54958                 me.setPosition(me.ghostPanel.getPosition());
54959             }
54960             if (me.floatingItems) {
54961                 me.floatingItems.show();
54962             }
54963             Ext.defer(me.focus, 10, me);
54964         }
54965         me.ghostPanel.el.hide();
54966     },
54967
54968     initResizable: function(resizable) {
54969         if (this.collapsed) {
54970             resizable.disabled = true;
54971         }
54972         this.callParent([resizable]);
54973     }
54974 });
54975
54976 /**
54977  * Component layout for Tip/ToolTip/etc. components
54978  * @class Ext.layout.component.Tip
54979  * @extends Ext.layout.component.Dock
54980  * @private
54981  */
54982
54983 Ext.define('Ext.layout.component.Tip', {
54984
54985     /* Begin Definitions */
54986
54987     alias: ['layout.tip'],
54988
54989     extend: 'Ext.layout.component.Dock',
54990
54991     /* End Definitions */
54992
54993     type: 'tip',
54994     
54995     onLayout: function(width, height) {
54996         var me = this,
54997             owner = me.owner,
54998             el = owner.el,
54999             minWidth,
55000             maxWidth,
55001             naturalWidth,
55002             constrainedWidth,
55003             xy = el.getXY();
55004
55005         // Position offscreen so the natural width is not affected by the viewport's right edge
55006         el.setXY([-9999,-9999]);
55007
55008         // Calculate initial layout
55009         this.callParent(arguments);
55010
55011         // Handle min/maxWidth for auto-width tips
55012         if (!Ext.isNumber(width)) {
55013             minWidth = owner.minWidth;
55014             maxWidth = owner.maxWidth;
55015             // IE6/7 in strict mode have a problem doing an autoWidth
55016             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
55017                 constrainedWidth = me.doAutoWidth();
55018             } else {
55019                 naturalWidth = el.getWidth();
55020             }
55021             if (naturalWidth < minWidth) {
55022                 constrainedWidth = minWidth;
55023             }
55024             else if (naturalWidth > maxWidth) {
55025                 constrainedWidth = maxWidth;
55026             }
55027             if (constrainedWidth) {
55028                 this.callParent([constrainedWidth, height]);
55029             }
55030         }
55031
55032         // Restore position
55033         el.setXY(xy);
55034     },
55035     
55036     doAutoWidth: function(){
55037         var me = this,
55038             owner = me.owner,
55039             body = owner.body,
55040             width = body.getTextWidth();
55041             
55042         if (owner.header) {
55043             width = Math.max(width, owner.header.getWidth());
55044         }
55045         if (!Ext.isDefined(me.frameWidth)) {
55046             me.frameWidth = owner.el.getWidth() - body.getWidth();
55047         }
55048         width += me.frameWidth + body.getPadding('lr');
55049         return width;
55050     }
55051 });
55052
55053 /**
55054  * @class Ext.tip.Tip
55055  * @extends Ext.panel.Panel
55056  * This is the base class for {@link Ext.tip.QuickTip} and {@link Ext.tip.ToolTip} that provides the basic layout and
55057  * positioning that all tip-based classes require. This class can be used directly for simple, statically-positioned
55058  * tips that are displayed programmatically, or it can be extended to provide custom tip implementations.
55059  * @constructor
55060  * Create a new Tip
55061  * @param {Object} config The configuration options
55062  * @xtype tip
55063  */
55064 Ext.define('Ext.tip.Tip', {
55065     extend: 'Ext.panel.Panel',
55066     requires: [ 'Ext.layout.component.Tip' ],
55067     alternateClassName: 'Ext.Tip',
55068     /**
55069      * @cfg {Boolean} closable True to render a close tool button into the tooltip header (defaults to false).
55070      */
55071     /**
55072      * @cfg {Number} width
55073      * Width in pixels of the tip (defaults to auto).  Width will be ignored if it exceeds the bounds of
55074      * {@link #minWidth} or {@link #maxWidth}.  The maximum supported value is 500.
55075      */
55076     /**
55077      * @cfg {Number} minWidth The minimum width of the tip in pixels (defaults to 40).
55078      */
55079     minWidth : 40,
55080     /**
55081      * @cfg {Number} maxWidth The maximum width of the tip in pixels (defaults to 300).  The maximum supported value is 500.
55082      */
55083     maxWidth : 300,
55084     /**
55085      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
55086      * for bottom-right shadow (defaults to "sides").
55087      */
55088     shadow : "sides",
55089
55090     /**
55091      * @cfg {String} defaultAlign <b>Experimental</b>. The default {@link Ext.core.Element#alignTo} anchor position value
55092      * for this tip relative to its element of origin (defaults to "tl-bl?").
55093      */
55094     defaultAlign : "tl-bl?",
55095     /**
55096      * @cfg {Boolean} constrainPosition If true, then the tooltip will be automatically constrained to stay within
55097      * the browser viewport. Defaults to false.
55098      */
55099     constrainPosition : true,
55100
55101     /**
55102      * @inherited
55103      */
55104     frame: false,
55105
55106     // private panel overrides
55107     autoRender: true,
55108     hidden: true,
55109     baseCls: Ext.baseCSSPrefix + 'tip',
55110     floating: {
55111         shadow: true,
55112         shim: true,
55113         constrain: true
55114     },
55115     focusOnToFront: false,
55116     componentLayout: 'tip',
55117
55118     closeAction: 'hide',
55119
55120     ariaRole: 'tooltip',
55121
55122     initComponent: function() {
55123         this.callParent(arguments);
55124
55125         // Or in the deprecated config. Floating.doConstrain only constrains if the constrain property is truthy.
55126         this.constrain = this.constrain || this.constrainPosition;
55127     },
55128
55129     /**
55130      * Shows this tip at the specified XY position.  Example usage:
55131      * <pre><code>
55132 // Show the tip at x:50 and y:100
55133 tip.showAt([50,100]);
55134 </code></pre>
55135      * @param {Array} xy An array containing the x and y coordinates
55136      */
55137     showAt : function(xy){
55138         var me = this;
55139         this.callParent();
55140         // Show may have been vetoed.
55141         if (me.isVisible()) {
55142             me.setPagePosition(xy[0], xy[1]);
55143             if (me.constrainPosition || me.constrain) {
55144                 me.doConstrain();
55145             }
55146             me.toFront(true);
55147         }
55148     },
55149
55150     /**
55151      * <b>Experimental</b>. Shows this tip at a position relative to another element using a standard {@link Ext.core.Element#alignTo}
55152      * anchor position value.  Example usage:
55153      * <pre><code>
55154 // Show the tip at the default position ('tl-br?')
55155 tip.showBy('my-el');
55156
55157 // Show the tip's top-left corner anchored to the element's top-right corner
55158 tip.showBy('my-el', 'tl-tr');
55159 </code></pre>
55160      * @param {Mixed} el An HTMLElement, Ext.core.Element or string id of the target element to align to
55161      * @param {String} position (optional) A valid {@link Ext.core.Element#alignTo} anchor position (defaults to 'tl-br?' or
55162      * {@link #defaultAlign} if specified).
55163      */
55164     showBy : function(el, pos) {
55165         this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign));
55166     },
55167
55168     /**
55169      * @private
55170      * @override
55171      * Set Tip draggable using base Component's draggability
55172      */
55173     initDraggable : function(){
55174         var me = this;
55175         me.draggable = {
55176             el: me.getDragEl(),
55177             delegate: me.header.el,
55178             constrain: me,
55179             constrainTo: me.el.dom.parentNode
55180         };
55181         // Important: Bypass Panel's initDraggable. Call direct to Component's implementation.
55182         Ext.Component.prototype.initDraggable.call(me);
55183     },
55184
55185     // Tip does not ghost. Drag is "live"
55186     ghost: undefined,
55187     unghost: undefined
55188 });
55189
55190 /**
55191  * @class Ext.tip.ToolTip
55192  * @extends Ext.tip.Tip
55193  * 
55194  * ToolTip is a {@link Ext.tip.Tip} implementation that handles the common case of displaying a
55195  * tooltip when hovering over a certain element or elements on the page. It allows fine-grained
55196  * control over the tooltip's alignment relative to the target element or mouse, and the timing
55197  * of when it is automatically shown and hidden.
55198  * 
55199  * This implementation does **not** have a built-in method of automatically populating the tooltip's
55200  * text based on the target element; you must either configure a fixed {@link #html} value for each
55201  * ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to
55202  * generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more
55203  * convenient way of automatically populating and configuring a tooltip based on specific DOM
55204  * attributes of each target element.
55205  * 
55206  * ## Basic Example
55207  * 
55208  *     var tip = Ext.create('Ext.tip.ToolTip', {
55209  *         target: 'clearButton',
55210  *         html: 'Press this button to clear the form'
55211  *     });
55212  * 
55213  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip1.png Basic Ext.tip.ToolTip}
55214  * 
55215  * ## Delegation
55216  * 
55217  * In addition to attaching a ToolTip to a single element, you can also use delegation to attach
55218  * one ToolTip to many elements under a common parent. This is more efficient than creating many
55219  * ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the
55220  * elements, and then set the {@link #delegate} config to a CSS selector that will select all the
55221  * appropriate sub-elements.
55222  * 
55223  * When using delegation, it is likely that you will want to programmatically change the content
55224  * of the ToolTip based on each delegate element; you can do this by implementing a custom
55225  * listener for the {@link #beforeshow} event. Example:
55226  * 
55227  *     var myGrid = Ext.create('Ext.grid.GridPanel', gridConfig);
55228  *     myGrid.on('render', function(grid) {
55229  *         var view = grid.getView();    // Capture the grid's view.
55230  *         grid.tip = Ext.create('Ext.tip.ToolTip', {
55231  *             target: view.el,          // The overall target element.
55232  *             delegate: view.itemSelector, // Each grid row causes its own seperate show and hide.
55233  *             trackMouse: true,         // Moving within the row should not hide the tip.
55234  *             renderTo: Ext.getBody(),  // Render immediately so that tip.body can be referenced prior to the first show.
55235  *             listeners: {              // Change content dynamically depending on which element triggered the show.
55236  *                 beforeshow: function updateTipBody(tip) {
55237  *                     tip.update('Over company "' + view.getRecord(tip.triggerElement).get('company') + '"');
55238  *                 }
55239  *             }
55240  *         });
55241  *     });
55242  * 
55243  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip2.png Ext.tip.ToolTip with delegation}
55244  * 
55245  * ## Alignment
55246  * 
55247  * The following configuration properties allow control over how the ToolTip is aligned relative to
55248  * the target element and/or mouse pointer:
55249  * 
55250  *  - {@link #anchor}
55251  *  - {@link #anchorToTarget}
55252  *  - {@link #anchorOffset}
55253  *  - {@link #trackMouse}
55254  *  - {@link #mouseOffset}
55255  * 
55256  * ## Showing/Hiding
55257  * 
55258  * The following configuration properties allow control over how and when the ToolTip is automatically
55259  * shown and hidden:
55260  * 
55261  *  - {@link #autoHide}
55262  *  - {@link #showDelay}
55263  *  - {@link #hideDelay}
55264  *  - {@link #dismissDelay}
55265  * 
55266  * @constructor
55267  * Create a new ToolTip instance
55268  * @param {Object} config The configuration options
55269  * @xtype tooltip
55270  * @markdown
55271  * @docauthor Jason Johnston <jason@sencha.com>
55272  */
55273 Ext.define('Ext.tip.ToolTip', {
55274     extend: 'Ext.tip.Tip',
55275     alias: 'widget.tooltip',
55276     alternateClassName: 'Ext.ToolTip',
55277     /**
55278      * When a ToolTip is configured with the <code>{@link #delegate}</code>
55279      * option to cause selected child elements of the <code>{@link #target}</code>
55280      * Element to each trigger a seperate show event, this property is set to
55281      * the DOM element which triggered the show.
55282      * @type DOMElement
55283      * @property triggerElement
55284      */
55285     /**
55286      * @cfg {Mixed} target The target HTMLElement, Ext.core.Element or id to monitor
55287      * for mouseover events to trigger showing this ToolTip.
55288      */
55289     /**
55290      * @cfg {Boolean} autoHide True to automatically hide the tooltip after the
55291      * mouse exits the target element or after the <code>{@link #dismissDelay}</code>
55292      * has expired if set (defaults to true).  If <code>{@link #closable} = true</code>
55293      * a close tool button will be rendered into the tooltip header.
55294      */
55295     /**
55296      * @cfg {Number} showDelay Delay in milliseconds before the tooltip displays
55297      * after the mouse enters the target element (defaults to 500)
55298      */
55299     showDelay: 500,
55300     /**
55301      * @cfg {Number} hideDelay Delay in milliseconds after the mouse exits the
55302      * target element but before the tooltip actually hides (defaults to 200).
55303      * Set to 0 for the tooltip to hide immediately.
55304      */
55305     hideDelay: 200,
55306     /**
55307      * @cfg {Number} dismissDelay Delay in milliseconds before the tooltip
55308      * automatically hides (defaults to 5000). To disable automatic hiding, set
55309      * dismissDelay = 0.
55310      */
55311     dismissDelay: 5000,
55312     /**
55313      * @cfg {Array} mouseOffset An XY offset from the mouse position where the
55314      * tooltip should be shown (defaults to [15,18]).
55315      */
55316     /**
55317      * @cfg {Boolean} trackMouse True to have the tooltip follow the mouse as it
55318      * moves over the target element (defaults to false).
55319      */
55320     trackMouse: false,
55321     /**
55322      * @cfg {String} anchor If specified, indicates that the tip should be anchored to a
55323      * particular side of the target element or mouse pointer ("top", "right", "bottom",
55324      * or "left"), with an arrow pointing back at the target or mouse pointer. If
55325      * {@link #constrainPosition} is enabled, this will be used as a preferred value
55326      * only and may be flipped as needed.
55327      */
55328     /**
55329      * @cfg {Boolean} anchorToTarget True to anchor the tooltip to the target
55330      * element, false to anchor it relative to the mouse coordinates (defaults
55331      * to true).  When <code>anchorToTarget</code> is true, use
55332      * <code>{@link #defaultAlign}</code> to control tooltip alignment to the
55333      * target element.  When <code>anchorToTarget</code> is false, use
55334      * <code>{@link #anchorPosition}</code> instead to control alignment.
55335      */
55336     anchorToTarget: true,
55337     /**
55338      * @cfg {Number} anchorOffset A numeric pixel value used to offset the
55339      * default position of the anchor arrow (defaults to 0).  When the anchor
55340      * position is on the top or bottom of the tooltip, <code>anchorOffset</code>
55341      * will be used as a horizontal offset.  Likewise, when the anchor position
55342      * is on the left or right side, <code>anchorOffset</code> will be used as
55343      * a vertical offset.
55344      */
55345     anchorOffset: 0,
55346     /**
55347      * @cfg {String} delegate <p>Optional. A {@link Ext.DomQuery DomQuery}
55348      * selector which allows selection of individual elements within the
55349      * <code>{@link #target}</code> element to trigger showing and hiding the
55350      * ToolTip as the mouse moves within the target.</p>
55351      * <p>When specified, the child element of the target which caused a show
55352      * event is placed into the <code>{@link #triggerElement}</code> property
55353      * before the ToolTip is shown.</p>
55354      * <p>This may be useful when a Component has regular, repeating elements
55355      * in it, each of which need a ToolTip which contains information specific
55356      * to that element. For example:</p><pre><code>
55357 var myGrid = Ext.create('Ext.grid.GridPanel', gridConfig);
55358 myGrid.on('render', function(grid) {
55359     var view = grid.getView();    // Capture the grid's view.
55360     grid.tip = Ext.create('Ext.tip.ToolTip', {
55361         target: view.el,          // The overall target element.
55362         delegate: view.itemSelector, // Each grid row causes its own seperate show and hide.
55363         trackMouse: true,         // Moving within the row should not hide the tip.
55364         renderTo: Ext.getBody(),  // Render immediately so that tip.body can be referenced prior to the first show.
55365         listeners: {              // Change content dynamically depending on which element triggered the show.
55366             beforeshow: function(tip) {
55367                 tip.update('Over Record ID ' + view.getRecord(tip.triggerElement).id);
55368             }
55369         }
55370     });
55371 });
55372      *</code></pre>
55373      */
55374
55375     // private
55376     targetCounter: 0,
55377     quickShowInterval: 250,
55378
55379     // private
55380     initComponent: function() {
55381         var me = this;
55382         me.callParent(arguments);
55383         me.lastActive = new Date();
55384         me.setTarget(me.target);
55385         me.origAnchor = me.anchor;
55386     },
55387
55388     // private
55389     onRender: function(ct, position) {
55390         var me = this;
55391         me.callParent(arguments);
55392         me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
55393         me.anchorEl = me.el.createChild({
55394             cls: Ext.baseCSSPrefix + 'tip-anchor ' + me.anchorCls
55395         });
55396     },
55397
55398     // private
55399     afterRender: function() {
55400         var me = this,
55401             zIndex;
55402
55403         me.callParent(arguments);
55404         zIndex = parseInt(me.el.getZIndex(), 10) || 0;
55405         me.anchorEl.setStyle('z-index', zIndex + 1).setVisibilityMode(Ext.core.Element.DISPLAY);
55406     },
55407
55408     /**
55409      * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
55410      * @param {Mixed} t The Element, HtmlElement, or ID of an element to bind to
55411      */
55412     setTarget: function(target) {
55413         var me = this,
55414             t = Ext.get(target),
55415             tg;
55416
55417         if (me.target) {
55418             tg = Ext.get(me.target);
55419             me.mun(tg, 'mouseover', me.onTargetOver, me);
55420             me.mun(tg, 'mouseout', me.onTargetOut, me);
55421             me.mun(tg, 'mousemove', me.onMouseMove, me);
55422         }
55423         
55424         me.target = t;
55425         if (t) {
55426             
55427             me.mon(t, {
55428                 // TODO - investigate why IE6/7 seem to fire recursive resize in e.getXY
55429                 // breaking QuickTip#onTargetOver (EXTJSIV-1608)
55430                 freezeEvent: true,
55431
55432                 mouseover: me.onTargetOver,
55433                 mouseout: me.onTargetOut,
55434                 mousemove: me.onMouseMove,
55435                 scope: me
55436             });
55437         }
55438         if (me.anchor) {
55439             me.anchorTarget = me.target;
55440         }
55441     },
55442
55443     // private
55444     onMouseMove: function(e) {
55445         var me = this,
55446             t = me.delegate ? e.getTarget(me.delegate) : me.triggerElement = true,
55447             xy;
55448         if (t) {
55449             me.targetXY = e.getXY();
55450             if (t === me.triggerElement) {
55451                 if (!me.hidden && me.trackMouse) {
55452                     xy = me.getTargetXY();
55453                     if (me.constrainPosition) {
55454                         xy = me.el.adjustForConstraints(xy, me.el.dom.parentNode);
55455                     }
55456                     me.setPagePosition(xy);
55457                 }
55458             } else {
55459                 me.hide();
55460                 me.lastActive = new Date(0);
55461                 me.onTargetOver(e);
55462             }
55463         } else if ((!me.closable && me.isVisible()) && me.autoHide !== false) {
55464             me.hide();
55465         }
55466     },
55467
55468     // private
55469     getTargetXY: function() {
55470         var me = this,
55471             mouseOffset;
55472         if (me.delegate) {
55473             me.anchorTarget = me.triggerElement;
55474         }
55475         if (me.anchor) {
55476             me.targetCounter++;
55477                 var offsets = me.getOffsets(),
55478                     xy = (me.anchorToTarget && !me.trackMouse) ? me.el.getAlignToXY(me.anchorTarget, me.getAnchorAlign()) : me.targetXY,
55479                     dw = Ext.core.Element.getViewWidth() - 5,
55480                     dh = Ext.core.Element.getViewHeight() - 5,
55481                     de = document.documentElement,
55482                     bd = document.body,
55483                     scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5,
55484                     scrollY = (de.scrollTop || bd.scrollTop || 0) + 5,
55485                     axy = [xy[0] + offsets[0], xy[1] + offsets[1]],
55486                     sz = me.getSize(),
55487                     constrainPosition = me.constrainPosition;
55488
55489             me.anchorEl.removeCls(me.anchorCls);
55490
55491             if (me.targetCounter < 2 && constrainPosition) {
55492                 if (axy[0] < scrollX) {
55493                     if (me.anchorToTarget) {
55494                         me.defaultAlign = 'l-r';
55495                         if (me.mouseOffset) {
55496                             me.mouseOffset[0] *= -1;
55497                         }
55498                     }
55499                     me.anchor = 'left';
55500                     return me.getTargetXY();
55501                 }
55502                 if (axy[0] + sz.width > dw) {
55503                     if (me.anchorToTarget) {
55504                         me.defaultAlign = 'r-l';
55505                         if (me.mouseOffset) {
55506                             me.mouseOffset[0] *= -1;
55507                         }
55508                     }
55509                     me.anchor = 'right';
55510                     return me.getTargetXY();
55511                 }
55512                 if (axy[1] < scrollY) {
55513                     if (me.anchorToTarget) {
55514                         me.defaultAlign = 't-b';
55515                         if (me.mouseOffset) {
55516                             me.mouseOffset[1] *= -1;
55517                         }
55518                     }
55519                     me.anchor = 'top';
55520                     return me.getTargetXY();
55521                 }
55522                 if (axy[1] + sz.height > dh) {
55523                     if (me.anchorToTarget) {
55524                         me.defaultAlign = 'b-t';
55525                         if (me.mouseOffset) {
55526                             me.mouseOffset[1] *= -1;
55527                         }
55528                     }
55529                     me.anchor = 'bottom';
55530                     return me.getTargetXY();
55531                 }
55532             }
55533
55534             me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
55535             me.anchorEl.addCls(me.anchorCls);
55536             me.targetCounter = 0;
55537             return axy;
55538         } else {
55539             mouseOffset = me.getMouseOffset();
55540             return (me.targetXY) ? [me.targetXY[0] + mouseOffset[0], me.targetXY[1] + mouseOffset[1]] : mouseOffset;
55541         }
55542     },
55543
55544     getMouseOffset: function() {
55545         var me = this,
55546         offset = me.anchor ? [0, 0] : [15, 18];
55547         if (me.mouseOffset) {
55548             offset[0] += me.mouseOffset[0];
55549             offset[1] += me.mouseOffset[1];
55550         }
55551         return offset;
55552     },
55553
55554     // private
55555     getAnchorPosition: function() {
55556         var me = this,
55557             m;
55558         if (me.anchor) {
55559             me.tipAnchor = me.anchor.charAt(0);
55560         } else {
55561             m = me.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
55562             if (!m) {
55563                 Ext.Error.raise('The AnchorTip.defaultAlign value "' + me.defaultAlign + '" is invalid.');
55564             }
55565             me.tipAnchor = m[1].charAt(0);
55566         }
55567
55568         switch (me.tipAnchor) {
55569         case 't':
55570             return 'top';
55571         case 'b':
55572             return 'bottom';
55573         case 'r':
55574             return 'right';
55575         }
55576         return 'left';
55577     },
55578
55579     // private
55580     getAnchorAlign: function() {
55581         switch (this.anchor) {
55582         case 'top':
55583             return 'tl-bl';
55584         case 'left':
55585             return 'tl-tr';
55586         case 'right':
55587             return 'tr-tl';
55588         default:
55589             return 'bl-tl';
55590         }
55591     },
55592
55593     // private
55594     getOffsets: function() {
55595         var me = this,
55596             mouseOffset,
55597             offsets,
55598             ap = me.getAnchorPosition().charAt(0);
55599         if (me.anchorToTarget && !me.trackMouse) {
55600             switch (ap) {
55601             case 't':
55602                 offsets = [0, 9];
55603                 break;
55604             case 'b':
55605                 offsets = [0, -13];
55606                 break;
55607             case 'r':
55608                 offsets = [ - 13, 0];
55609                 break;
55610             default:
55611                 offsets = [9, 0];
55612                 break;
55613             }
55614         } else {
55615             switch (ap) {
55616             case 't':
55617                 offsets = [ - 15 - me.anchorOffset, 30];
55618                 break;
55619             case 'b':
55620                 offsets = [ - 19 - me.anchorOffset, -13 - me.el.dom.offsetHeight];
55621                 break;
55622             case 'r':
55623                 offsets = [ - 15 - me.el.dom.offsetWidth, -13 - me.anchorOffset];
55624                 break;
55625             default:
55626                 offsets = [25, -13 - me.anchorOffset];
55627                 break;
55628             }
55629         }
55630         mouseOffset = me.getMouseOffset();
55631         offsets[0] += mouseOffset[0];
55632         offsets[1] += mouseOffset[1];
55633
55634         return offsets;
55635     },
55636
55637     // private
55638     onTargetOver: function(e) {
55639         var me = this,
55640             t;
55641
55642         if (me.disabled || e.within(me.target.dom, true)) {
55643             return;
55644         }
55645         t = e.getTarget(me.delegate);
55646         if (t) {
55647             me.triggerElement = t;
55648             me.clearTimer('hide');
55649             me.targetXY = e.getXY();
55650             me.delayShow();
55651         }
55652     },
55653
55654     // private
55655     delayShow: function() {
55656         var me = this;
55657         if (me.hidden && !me.showTimer) {
55658             if (Ext.Date.getElapsed(me.lastActive) < me.quickShowInterval) {
55659                 me.show();
55660             } else {
55661                 me.showTimer = Ext.defer(me.show, me.showDelay, me);
55662             }
55663         }
55664         else if (!me.hidden && me.autoHide !== false) {
55665             me.show();
55666         }
55667     },
55668
55669     // private
55670     onTargetOut: function(e) {
55671         var me = this;
55672         if (me.disabled || e.within(me.target.dom, true)) {
55673             return;
55674         }
55675         me.clearTimer('show');
55676         if (me.autoHide !== false) {
55677             me.delayHide();
55678         }
55679     },
55680
55681     // private
55682     delayHide: function() {
55683         var me = this;
55684         if (!me.hidden && !me.hideTimer) {
55685             me.hideTimer = Ext.defer(me.hide, me.hideDelay, me);
55686         }
55687     },
55688
55689     /**
55690      * Hides this tooltip if visible.
55691      */
55692     hide: function() {
55693         var me = this;
55694         me.clearTimer('dismiss');
55695         me.lastActive = new Date();
55696         if (me.anchorEl) {
55697             me.anchorEl.hide();
55698         }
55699         me.callParent(arguments);
55700         delete me.triggerElement;
55701     },
55702
55703     /**
55704      * Shows this tooltip at the current event target XY position.
55705      */
55706     show: function() {
55707         var me = this;
55708
55709         // Show this Component first, so that sizing can be calculated
55710         // pre-show it off screen so that the el will have dimensions
55711         this.callParent();
55712         if (this.hidden === false) {
55713             me.setPagePosition(-10000, -10000);
55714
55715             if (me.anchor) {
55716                 me.anchor = me.origAnchor;
55717             }
55718             me.showAt(me.getTargetXY());
55719
55720             if (me.anchor) {
55721                 me.syncAnchor();
55722                 me.anchorEl.show();
55723             } else {
55724                 me.anchorEl.hide();
55725             }
55726         }
55727     },
55728
55729     // inherit docs
55730     showAt: function(xy) {
55731         var me = this;
55732         me.lastActive = new Date();
55733         me.clearTimers();
55734
55735         // Only call if this is hidden. May have been called from show above.
55736         if (!me.isVisible()) {
55737             this.callParent(arguments);
55738         }
55739
55740         // Show may have been vetoed.
55741         if (me.isVisible()) {
55742             me.setPagePosition(xy[0], xy[1]);
55743             if (me.constrainPosition || me.constrain) {
55744                 me.doConstrain();
55745             }
55746             me.toFront(true);
55747         }
55748
55749         if (me.dismissDelay && me.autoHide !== false) {
55750             me.dismissTimer = Ext.defer(me.hide, me.dismissDelay, me);
55751         }
55752         if (me.anchor) {
55753             me.syncAnchor();
55754             if (!me.anchorEl.isVisible()) {
55755                 me.anchorEl.show();
55756             }
55757         } else {
55758             me.anchorEl.hide();
55759         }
55760     },
55761
55762     // private
55763     syncAnchor: function() {
55764         var me = this,
55765             anchorPos,
55766             targetPos,
55767             offset;
55768         switch (me.tipAnchor.charAt(0)) {
55769         case 't':
55770             anchorPos = 'b';
55771             targetPos = 'tl';
55772             offset = [20 + me.anchorOffset, 1];
55773             break;
55774         case 'r':
55775             anchorPos = 'l';
55776             targetPos = 'tr';
55777             offset = [ - 1, 12 + me.anchorOffset];
55778             break;
55779         case 'b':
55780             anchorPos = 't';
55781             targetPos = 'bl';
55782             offset = [20 + me.anchorOffset, -1];
55783             break;
55784         default:
55785             anchorPos = 'r';
55786             targetPos = 'tl';
55787             offset = [1, 12 + me.anchorOffset];
55788             break;
55789         }
55790         me.anchorEl.alignTo(me.el, anchorPos + '-' + targetPos, offset);
55791     },
55792
55793     // private
55794     setPagePosition: function(x, y) {
55795         var me = this;
55796         me.callParent(arguments);
55797         if (me.anchor) {
55798             me.syncAnchor();
55799         }
55800     },
55801
55802     // private
55803     clearTimer: function(name) {
55804         name = name + 'Timer';
55805         clearTimeout(this[name]);
55806         delete this[name];
55807     },
55808
55809     // private
55810     clearTimers: function() {
55811         var me = this;
55812         me.clearTimer('show');
55813         me.clearTimer('dismiss');
55814         me.clearTimer('hide');
55815     },
55816
55817     // private
55818     onShow: function() {
55819         var me = this;
55820         me.callParent();
55821         me.mon(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
55822     },
55823
55824     // private
55825     onHide: function() {
55826         var me = this;
55827         me.callParent();
55828         me.mun(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
55829     },
55830
55831     // private
55832     onDocMouseDown: function(e) {
55833         var me = this;
55834         if (me.autoHide !== true && !me.closable && !e.within(me.el.dom)) {
55835             me.disable();
55836             Ext.defer(me.doEnable, 100, me);
55837         }
55838     },
55839
55840     // private
55841     doEnable: function() {
55842         if (!this.isDestroyed) {
55843             this.enable();
55844         }
55845     },
55846
55847     // private
55848     onDisable: function() {
55849         this.callParent();
55850         this.clearTimers();
55851         this.hide();
55852     },
55853
55854     beforeDestroy: function() {
55855         var me = this;
55856         me.clearTimers();
55857         Ext.destroy(me.anchorEl);
55858         delete me.anchorEl;
55859         delete me.target;
55860         delete me.anchorTarget;
55861         delete me.triggerElement;
55862         me.callParent();
55863     },
55864
55865     // private
55866     onDestroy: function() {
55867         Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
55868         this.callParent();
55869     }
55870 });
55871
55872 /**
55873  * @class Ext.tip.QuickTip
55874  * @extends Ext.tip.ToolTip
55875  * A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global
55876  * {@link Ext.tip.QuickTipManager} instance.  See the QuickTipManager class header for additional usage details and examples.
55877  * @constructor
55878  * Create a new Tip
55879  * @param {Object} config The configuration options
55880  * @xtype quicktip
55881  */
55882 Ext.define('Ext.tip.QuickTip', {
55883     extend: 'Ext.tip.ToolTip',
55884     alternateClassName: 'Ext.QuickTip',
55885     /**
55886      * @cfg {Mixed} target The target HTMLElement, Ext.core.Element or id to associate with this Quicktip (defaults to the document).
55887      */
55888     /**
55889      * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available (defaults to false).
55890      */
55891     interceptTitles : false,
55892
55893     // Force creation of header Component
55894     title: '&#160;',
55895
55896     // private
55897     tagConfig : {
55898         namespace : "data-",
55899         attribute : "qtip",
55900         width : "qwidth",
55901         target : "target",
55902         title : "qtitle",
55903         hide : "hide",
55904         cls : "qclass",
55905         align : "qalign",
55906         anchor : "anchor"
55907     },
55908
55909     // private
55910     initComponent : function(){
55911         var me = this;
55912         
55913         me.target = me.target || Ext.getDoc();
55914         me.targets = me.targets || {};
55915         me.callParent();
55916     },
55917
55918     /**
55919      * Configures a new quick tip instance and assigns it to a target element.  The following config values are
55920      * supported (for example usage, see the {@link Ext.tip.QuickTipManager} class header):
55921      * <div class="mdetail-params"><ul>
55922      * <li>autoHide</li>
55923      * <li>cls</li>
55924      * <li>dismissDelay (overrides the singleton value)</li>
55925      * <li>target (required)</li>
55926      * <li>text (required)</li>
55927      * <li>title</li>
55928      * <li>width</li></ul></div>
55929      * @param {Object} config The config object
55930      */
55931     register : function(config){
55932         var configs = Ext.isArray(config) ? config : arguments,
55933             i = 0,
55934             len = configs.length,
55935             target, j, targetLen;
55936             
55937         for (; i < len; i++) {
55938             config = configs[i];
55939             target = config.target;
55940             if (target) {
55941                 if (Ext.isArray(target)) {
55942                     for (j = 0, targetLen = target.length; j < targetLen; j++) {
55943                         this.targets[Ext.id(target[j])] = config;
55944                     }
55945                 } else{
55946                     this.targets[Ext.id(target)] = config;
55947                 }
55948             }
55949         }
55950     },
55951
55952     /**
55953      * Removes this quick tip from its element and destroys it.
55954      * @param {String/HTMLElement/Element} el The element from which the quick tip is to be removed.
55955      */
55956     unregister : function(el){
55957         delete this.targets[Ext.id(el)];
55958     },
55959     
55960     /**
55961      * Hides a visible tip or cancels an impending show for a particular element.
55962      * @param {String/HTMLElement/Element} el The element that is the target of the tip.
55963      */
55964     cancelShow: function(el){
55965         var me = this,
55966             activeTarget = me.activeTarget;
55967             
55968         el = Ext.get(el).dom;
55969         if (me.isVisible()) {
55970             if (activeTarget && activeTarget.el == el) {
55971                 me.hide();
55972             }
55973         } else if (activeTarget && activeTarget.el == el) {
55974             me.clearTimer('show');
55975         }
55976     },
55977     
55978     getTipCfg: function(e) {
55979         var t = e.getTarget(),
55980             ttp, 
55981             cfg;
55982         
55983         if(this.interceptTitles && t.title && Ext.isString(t.title)){
55984             ttp = t.title;
55985             t.qtip = ttp;
55986             t.removeAttribute("title");
55987             e.preventDefault();
55988         } 
55989         else {            
55990             cfg = this.tagConfig;
55991             t = e.getTarget('[' + cfg.namespace + cfg.attribute + ']');
55992             if (t) {
55993                 ttp = t.getAttribute(cfg.namespace + cfg.attribute);
55994             }
55995         }
55996         return ttp;
55997     },
55998
55999     // private
56000     onTargetOver : function(e){
56001         var me = this,
56002             target = e.getTarget(),
56003             elTarget,
56004             cfg,
56005             ns,
56006             ttp,
56007             autoHide;
56008         
56009         if (me.disabled) {
56010             return;
56011         }
56012
56013         // TODO - this causes "e" to be recycled in IE6/7 (EXTJSIV-1608) so ToolTip#setTarget
56014         // was changed to include freezeEvent. The issue seems to be a nested 'resize' event
56015         // that smashed Ext.EventObject.
56016         me.targetXY = e.getXY();
56017
56018         if(!target || target.nodeType !== 1 || target == document || target == document.body){
56019             return;
56020         }
56021         
56022         if (me.activeTarget && ((target == me.activeTarget.el) || Ext.fly(me.activeTarget.el).contains(target))) {
56023             me.clearTimer('hide');
56024             me.show();
56025             return;
56026         }
56027         
56028         if (target) {
56029             Ext.Object.each(me.targets, function(key, value) {
56030                 var targetEl = Ext.fly(value.target);
56031                 if (targetEl && (targetEl.dom === target || targetEl.contains(target))) {
56032                     elTarget = targetEl.dom;
56033                     return false;
56034                 }
56035             });
56036             if (elTarget) {
56037                 me.activeTarget = me.targets[elTarget.id];
56038                 me.activeTarget.el = target;
56039                 me.anchor = me.activeTarget.anchor;
56040                 if (me.anchor) {
56041                     me.anchorTarget = target;
56042                 }
56043                 me.delayShow();
56044                 return;
56045             }
56046         }
56047
56048         elTarget = Ext.get(target);
56049         cfg = me.tagConfig;
56050         ns = cfg.namespace; 
56051         ttp = me.getTipCfg(e);
56052         
56053         if (ttp) {
56054             autoHide = elTarget.getAttribute(ns + cfg.hide);
56055                  
56056             me.activeTarget = {
56057                 el: target,
56058                 text: ttp,
56059                 width: +elTarget.getAttribute(ns + cfg.width) || null,
56060                 autoHide: autoHide != "user" && autoHide !== 'false',
56061                 title: elTarget.getAttribute(ns + cfg.title),
56062                 cls: elTarget.getAttribute(ns + cfg.cls),
56063                 align: elTarget.getAttribute(ns + cfg.align)
56064                 
56065             };
56066             me.anchor = elTarget.getAttribute(ns + cfg.anchor);
56067             if (me.anchor) {
56068                 me.anchorTarget = target;
56069             }
56070             me.delayShow();
56071         }
56072     },
56073
56074     // private
56075     onTargetOut : function(e){
56076         var me = this;
56077         
56078         // If moving within the current target, and it does not have a new tip, ignore the mouseout
56079         if (me.activeTarget && e.within(me.activeTarget.el) && !me.getTipCfg(e)) {
56080             return;
56081         }
56082
56083         me.clearTimer('show');
56084         if (me.autoHide !== false) {
56085             me.delayHide();
56086         }
56087     },
56088
56089     // inherit docs
56090     showAt : function(xy){
56091         var me = this,
56092             target = me.activeTarget;
56093         
56094         if (target) {
56095             if (!me.rendered) {
56096                 me.render(Ext.getBody());
56097                 me.activeTarget = target;
56098             }
56099             if (target.title) {
56100                 me.setTitle(target.title || '');
56101                 me.header.show();
56102             } else {
56103                 me.header.hide();
56104             }
56105             me.body.update(target.text);
56106             me.autoHide = target.autoHide;
56107             me.dismissDelay = target.dismissDelay || me.dismissDelay;
56108             if (me.lastCls) {
56109                 me.el.removeCls(me.lastCls);
56110                 delete me.lastCls;
56111             }
56112             if (target.cls) {
56113                 me.el.addCls(target.cls);
56114                 me.lastCls = target.cls;
56115             }
56116
56117             me.setWidth(target.width);
56118             
56119             if (me.anchor) {
56120                 me.constrainPosition = false;
56121             } else if (target.align) { // TODO: this doesn't seem to work consistently
56122                 xy = me.el.getAlignToXY(target.el, target.align);
56123                 me.constrainPosition = false;
56124             }else{
56125                 me.constrainPosition = true;
56126             }
56127         }
56128         me.callParent([xy]);
56129     },
56130
56131     // inherit docs
56132     hide: function(){
56133         delete this.activeTarget;
56134         this.callParent();
56135     }
56136 });
56137
56138 /**
56139  * @class Ext.tip.QuickTipManager
56140  *
56141  * Provides attractive and customizable tooltips for any element. The QuickTips
56142  * singleton is used to configure and manage tooltips globally for multiple elements
56143  * in a generic manner.  To create individual tooltips with maximum customizability,
56144  * you should consider either {@link Ext.tip.Tip} or {@link Ext.tip.ToolTip}.
56145  *
56146  * Quicktips can be configured via tag attributes directly in markup, or by
56147  * registering quick tips programmatically via the {@link #register} method.
56148  *
56149  * The singleton's instance of {@link Ext.tip.QuickTip} is available via
56150  * {@link #getQuickTip}, and supports all the methods, and all the all the
56151  * configuration properties of Ext.tip.QuickTip. These settings will apply to all
56152  * tooltips shown by the singleton.
56153  *
56154  * Below is the summary of the configuration properties which can be used.
56155  * For detailed descriptions see the config options for the {@link Ext.tip.QuickTip QuickTip} class
56156  *
56157  * ## QuickTips singleton configs (all are optional)
56158  *
56159  *  - `dismissDelay`
56160  *  - `hideDelay`
56161  *  - `maxWidth`
56162  *  - `minWidth`
56163  *  - `showDelay`
56164  *  - `trackMouse`
56165  *
56166  * ## Target element configs (optional unless otherwise noted)
56167  *
56168  *  - `autoHide`
56169  *  - `cls`
56170  *  - `dismissDelay` (overrides singleton value)
56171  *  - `target` (required)
56172  *  - `text` (required)
56173  *  - `title`
56174  *  - `width`
56175  *
56176  * Here is an example showing how some of these config options could be used:
56177  *
56178  * {@img Ext.tip.QuickTipManager/Ext.tip.QuickTipManager.png Ext.tip.QuickTipManager component}
56179  *
56180  * ## Code
56181  *
56182  *     // Init the singleton.  Any tag-based quick tips will start working.
56183  *     Ext.tip.QuickTipManager.init();
56184  *     
56185  *     // Apply a set of config properties to the singleton
56186  *     Ext.apply(Ext.tip.QuickTipManager.getQuickTip(), {
56187  *         maxWidth: 200,
56188  *         minWidth: 100,
56189  *         showDelay: 50      // Show 50ms after entering target
56190  *     });
56191  *     
56192  *     // Create a small panel to add a quick tip to
56193  *     Ext.create('Ext.container.Container', {
56194  *         id: 'quickTipContainer',
56195  *         width: 200,
56196  *         height: 150,
56197  *         style: {
56198  *             backgroundColor:'#000000'
56199  *         },
56200  *         renderTo: Ext.getBody()
56201  *     });
56202  *     
56203  *     
56204  *     // Manually register a quick tip for a specific element
56205  *     Ext.tip.QuickTipManager.register({
56206  *         target: 'quickTipContainer',
56207  *         title: 'My Tooltip',
56208  *         text: 'This tooltip was added in code',
56209  *         width: 100,
56210  *         dismissDelay: 10000 // Hide after 10 seconds hover
56211  *     });
56212  *
56213  * To register a quick tip in markup, you simply add one or more of the valid QuickTip attributes prefixed with
56214  * the **data-** namespace.  The HTML element itself is automatically set as the quick tip target. Here is the summary
56215  * of supported attributes (optional unless otherwise noted):
56216  *
56217  *  - `hide`: Specifying "user" is equivalent to setting autoHide = false.  Any other value will be the same as autoHide = true.
56218  *  - `qclass`: A CSS class to be applied to the quick tip (equivalent to the 'cls' target element config).
56219  *  - `qtip (required)`: The quick tip text (equivalent to the 'text' target element config).
56220  *  - `qtitle`: The quick tip title (equivalent to the 'title' target element config).
56221  *  - `qwidth`: The quick tip width (equivalent to the 'width' target element config).
56222  *
56223  * Here is an example of configuring an HTML element to display a tooltip from markup:
56224  *     
56225  *     // Add a quick tip to an HTML button
56226  *     <input type="button" value="OK" data-qtitle="OK Button" data-qwidth="100"
56227  *          data-qtip="This is a quick tip from markup!"></input>
56228  *
56229  * @singleton
56230  */
56231 Ext.define('Ext.tip.QuickTipManager', function() {
56232     var tip,
56233         disabled = false;
56234
56235     return {
56236         requires: ['Ext.tip.QuickTip'],
56237         singleton: true,
56238         alternateClassName: 'Ext.QuickTips',
56239
56240         /**
56241          * Initialize the global QuickTips instance and prepare any quick tips.
56242          * @param {Boolean} autoRender True to render the QuickTips container immediately to
56243          * preload images. (Defaults to true)
56244          * @param {Object} config An optional config object for the created QuickTip. By
56245          * default, the {@link Ext.tip.QuickTip QuickTip} class is instantiated, but this can
56246          * be changed by supplying an xtype property or a className property in this object.
56247          * All other properties on this object are configuration for the created component.
56248          */
56249         init : function (autoRender, config) {
56250             if (!tip) {
56251                 if (!Ext.isReady) {
56252                     Ext.onReady(function(){
56253                         Ext.tip.QuickTipManager.init(autoRender);
56254                     });
56255                     return;
56256                 }
56257
56258                 var tipConfig = Ext.apply({ disabled: disabled }, config),
56259                     className = tipConfig.className,
56260                     xtype = tipConfig.xtype;
56261
56262                 if (className) {
56263                     delete tipConfig.className;
56264                 } else if (xtype) {
56265                     className = 'widget.' + xtype;
56266                     delete tipConfig.xtype;
56267                 }
56268
56269                 if (autoRender !== false) {
56270                     tipConfig.renderTo = document.body;
56271
56272                     if (tipConfig.renderTo.tagName != 'BODY') { // e.g., == 'FRAMESET'
56273                         Ext.Error.raise({
56274                             sourceClass: 'Ext.tip.QuickTipManager',
56275                             sourceMethod: 'init',
56276                             msg: 'Cannot init QuickTipManager: no document body'
56277                         });
56278                     }
56279                 }
56280
56281                 tip = Ext.create(className || 'Ext.tip.QuickTip', tipConfig);
56282             }
56283         },
56284
56285         /**
56286          * Destroy the QuickTips instance.
56287          */
56288         destroy: function() {
56289             if (tip) {
56290                 var undef;
56291                 tip.destroy();
56292                 tip = undef;
56293             }
56294         },
56295
56296         // Protected method called by the dd classes
56297         ddDisable : function(){
56298             // don't disable it if we don't need to
56299             if(tip && !disabled){
56300                 tip.disable();
56301             }
56302         },
56303
56304         // Protected method called by the dd classes
56305         ddEnable : function(){
56306             // only enable it if it hasn't been disabled
56307             if(tip && !disabled){
56308                 tip.enable();
56309             }
56310         },
56311
56312         /**
56313          * Enable quick tips globally.
56314          */
56315         enable : function(){
56316             if(tip){
56317                 tip.enable();
56318             }
56319             disabled = false;
56320         },
56321
56322         /**
56323          * Disable quick tips globally.
56324          */
56325         disable : function(){
56326             if(tip){
56327                 tip.disable();
56328             }
56329             disabled = true;
56330         },
56331
56332         /**
56333          * Returns true if quick tips are enabled, else false.
56334          * @return {Boolean}
56335          */
56336         isEnabled : function(){
56337             return tip !== undefined && !tip.disabled;
56338         },
56339
56340         /**
56341          * Gets the single {@link Ext.tip.QuickTip QuickTip} instance used to show tips from all registered elements.
56342          * @return {Ext.tip.QuickTip}
56343          */
56344         getQuickTip : function(){
56345             return tip;
56346         },
56347
56348         /**
56349          * Configures a new quick tip instance and assigns it to a target element.  See
56350          * {@link Ext.tip.QuickTip#register} for details.
56351          * @param {Object} config The config object
56352          */
56353         register : function(){
56354             tip.register.apply(tip, arguments);
56355         },
56356
56357         /**
56358          * Removes any registered quick tip from the target element and destroys it.
56359          * @param {String/HTMLElement/Element} el The element from which the quick tip is to be removed.
56360          */
56361         unregister : function(){
56362             tip.unregister.apply(tip, arguments);
56363         },
56364
56365         /**
56366          * Alias of {@link #register}.
56367          * @param {Object} config The config object
56368          */
56369         tips : function(){
56370             tip.register.apply(tip, arguments);
56371         }
56372     };
56373 }());
56374 /**
56375  * @class Ext.app.Application
56376  * @extend Ext.app.Controller
56377  * 
56378  * Represents an Ext JS 4 application, which is typically a single page app using a {@link Ext.container.Viewport Viewport}.
56379  * A typical Ext.app.Application might look like this:
56380  * 
56381  *     Ext.application({
56382  *         name: 'MyApp',
56383  *         launch: function() {
56384  *             Ext.create('Ext.container.Viewport', {
56385  *                 items: {
56386  *                     html: 'My App'
56387  *                 }
56388  *             });
56389  *         }
56390  *     });
56391  * 
56392  * This does several things. First it creates a global variable called 'MyApp' - all of your Application's classes (such
56393  * as its Models, Views and Controllers) will reside under this single namespace, which drastically lowers the chances
56394  * of colliding global variables.
56395  * 
56396  * When the page is ready and all of your JavaScript has loaded, your Application's {@link #launch} function is called,
56397  * at which time you can run the code that starts your app. Usually this consists of creating a Viewport, as we do in
56398  * the example above.
56399  * 
56400  * <u>Telling Application about the rest of the app</u>
56401  * 
56402  * Because an Ext.app.Application represents an entire app, we should tell it about the other parts of the app - namely
56403  * the Models, Views and Controllers that are bundled with the application. Let's say we have a blog management app; we
56404  * might have Models and Controllers for Posts and Comments, and Views for listing, adding and editing Posts and Comments.
56405  * Here's how we'd tell our Application about all these things:
56406  * 
56407  *     Ext.application({
56408  *         name: 'Blog',
56409  *         models: ['Post', 'Comment'],
56410  *         controllers: ['Posts', 'Comments'],
56411  *     
56412  *         launch: function() {
56413  *             ...
56414  *         }
56415  *     });
56416  * 
56417  * Note that we didn't actually list the Views directly in the Application itself. This is because Views are managed by
56418  * Controllers, so it makes sense to keep those dependencies there. The Application will load each of the specified 
56419  * Controllers using the pathing conventions laid out in the <a href="../guide/application_architecture">application 
56420  * architecture guide</a> - in this case expecting the controllers to reside in app/controller/Posts.js and
56421  * app/controller/Comments.js. In turn, each Controller simply needs to list the Views it uses and they will be
56422  * automatically loaded. Here's how our Posts controller like be defined:
56423  * 
56424  *     Ext.define('MyApp.controller.Posts', {
56425  *         extend: 'Ext.app.Controller',
56426  *         views: ['posts.List', 'posts.Edit'],
56427  *     
56428  *         //the rest of the Controller here
56429  *     });
56430  * 
56431  * Because we told our Application about our Models and Controllers, and our Controllers about their Views, Ext JS will
56432  * automatically load all of our app files for us. This means we don't have to manually add script tags into our html
56433  * files whenever we add a new class, but more importantly it enables us to create a minimized build of our entire 
56434  * application using the Ext JS 4 SDK Tools.
56435  * 
56436  * For more information about writing Ext JS 4 applications, please see the <a href="../guide/application_architecture">
56437  * application architecture guide</a>.
56438  * 
56439  * @docauthor Ed Spencer
56440  * @constructor
56441  */
56442 Ext.define('Ext.app.Application', {
56443     extend: 'Ext.app.Controller',
56444
56445     requires: [
56446         'Ext.ModelManager',
56447         'Ext.data.Model',
56448         'Ext.data.StoreManager',
56449         'Ext.tip.QuickTipManager',
56450         'Ext.ComponentManager',
56451         'Ext.app.EventBus'
56452     ],
56453
56454     /**
56455      * @cfg {String} name The name of your application. This will also be the namespace for your views, controllers
56456      * models and stores. Don't use spaces or special characters in the name.
56457      */
56458
56459     /**
56460      * @cfg {Object} scope The scope to execute the {@link #launch} function in. Defaults to the Application
56461      * instance.
56462      */
56463     scope: undefined,
56464
56465     /**
56466      * @cfg {Boolean} enableQuickTips True to automatically set up Ext.tip.QuickTip support (defaults to true)
56467      */
56468     enableQuickTips: true,
56469
56470     /**
56471      * @cfg {String} defaultUrl When the app is first loaded, this url will be redirected to. Defaults to undefined
56472      */
56473
56474     /**
56475      * @cfg {String} appFolder The path to the directory which contains all application's classes.
56476      * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
56477      * Defaults to 'app'
56478      */
56479     appFolder: 'app',
56480
56481     /**
56482      * @cfg {Boolean} autoCreateViewport True to automatically load and instantiate AppName.view.Viewport
56483      * before firing the launch function (defaults to false).
56484      */
56485     autoCreateViewport: false,
56486
56487     constructor: function(config) {
56488         config = config || {};
56489         Ext.apply(this, config);
56490
56491         var requires = config.requires || [];
56492
56493         Ext.Loader.setPath(this.name, this.appFolder);
56494
56495         if (this.paths) {
56496             Ext.Object.each(this.paths, function(key, value) {
56497                 Ext.Loader.setPath(key, value);
56498             });
56499         }
56500
56501         this.callParent(arguments);
56502
56503         this.eventbus = Ext.create('Ext.app.EventBus');
56504
56505         var controllers = Ext.Array.from(this.controllers),
56506             ln = controllers && controllers.length,
56507             i, controller;
56508
56509         this.controllers = Ext.create('Ext.util.MixedCollection');
56510
56511         if (this.autoCreateViewport) {
56512             requires.push(this.getModuleClassName('Viewport', 'view'));
56513         }
56514
56515         for (i = 0; i < ln; i++) {
56516             requires.push(this.getModuleClassName(controllers[i], 'controller'));
56517         }
56518
56519         Ext.require(requires);
56520
56521         Ext.onReady(function() {
56522             for (i = 0; i < ln; i++) {
56523                 controller = this.getController(controllers[i]);
56524                 controller.init(this);
56525             }
56526
56527             this.onBeforeLaunch.call(this);
56528         }, this);
56529     },
56530
56531     control: function(selectors, listeners, controller) {
56532         this.eventbus.control(selectors, listeners, controller);
56533     },
56534
56535     /**
56536      * Called automatically when the page has completely loaded. This is an empty function that should be
56537      * overridden by each application that needs to take action on page load
56538      * @property launch
56539      * @type Function
56540      * @param {String} profile The detected {@link #profiles application profile}
56541      * @return {Boolean} By default, the Application will dispatch to the configured startup controller and
56542      * action immediately after running the launch function. Return false to prevent this behavior.
56543      */
56544     launch: Ext.emptyFn,
56545
56546     /**
56547      * @private
56548      */
56549     onBeforeLaunch: function() {
56550         if (this.enableQuickTips) {
56551             Ext.tip.QuickTipManager.init();
56552         }
56553
56554         if (this.autoCreateViewport) {
56555             this.getView('Viewport').create();
56556         }
56557
56558         this.launch.call(this.scope || this);
56559         this.launched = true;
56560         this.fireEvent('launch', this);
56561
56562         this.controllers.each(function(controller) {
56563             controller.onLaunch(this);
56564         }, this);
56565     },
56566
56567     getModuleClassName: function(name, type) {
56568         var namespace = Ext.Loader.getPrefix(name);
56569
56570         if (namespace.length > 0 && namespace !== name) {
56571             return name;
56572         }
56573
56574         return this.name + '.' + type + '.' + name;
56575     },
56576
56577     getController: function(name) {
56578         var controller = this.controllers.get(name);
56579
56580         if (!controller) {
56581             controller = Ext.create(this.getModuleClassName(name, 'controller'), {
56582                 application: this,
56583                 id: name
56584             });
56585
56586             this.controllers.add(controller);
56587         }
56588
56589         return controller;
56590     },
56591
56592     getStore: function(name) {
56593         var store = Ext.StoreManager.get(name);
56594
56595         if (!store) {
56596             store = Ext.create(this.getModuleClassName(name, 'store'), {
56597                 storeId: name
56598             });
56599         }
56600
56601         return store;
56602     },
56603
56604     getModel: function(model) {
56605         model = this.getModuleClassName(model, 'model');
56606
56607         return Ext.ModelManager.getModel(model);
56608     },
56609
56610     getView: function(view) {
56611         view = this.getModuleClassName(view, 'view');
56612
56613         return Ext.ClassManager.get(view);
56614     }
56615 });
56616
56617 /**
56618  * @class Ext.chart.Callout
56619  * @ignore
56620  */
56621 Ext.define('Ext.chart.Callout', {
56622
56623     /* Begin Definitions */
56624
56625     /* End Definitions */
56626
56627     constructor: function(config) {
56628         if (config.callouts) {
56629             config.callouts.styles = Ext.applyIf(config.callouts.styles || {}, {
56630                 color: "#000",
56631                 font: "11px Helvetica, sans-serif"
56632             });
56633             this.callouts = Ext.apply(this.callouts || {}, config.callouts);
56634             this.calloutsArray = [];
56635         }
56636     },
56637
56638     renderCallouts: function() {
56639         if (!this.callouts) {
56640             return;
56641         }
56642
56643         var me = this,
56644             items = me.items,
56645             animate = me.chart.animate,
56646             config = me.callouts,
56647             styles = config.styles,
56648             group = me.calloutsArray,
56649             store = me.chart.store,
56650             len = store.getCount(),
56651             ratio = items.length / len,
56652             previouslyPlacedCallouts = [],
56653             i,
56654             count,
56655             j,
56656             p;
56657             
56658         for (i = 0, count = 0; i < len; i++) {
56659             for (j = 0; j < ratio; j++) {
56660                 var item = items[count],
56661                     label = group[count],
56662                     storeItem = store.getAt(i),
56663                     display;
56664                 
56665                 display = config.filter(storeItem);
56666                 
56667                 if (!display && !label) {
56668                     count++;
56669                     continue;               
56670                 }
56671                 
56672                 if (!label) {
56673                     group[count] = label = me.onCreateCallout(storeItem, item, i, display, j, count);
56674                 }
56675                 for (p in label) {
56676                     if (label[p] && label[p].setAttributes) {
56677                         label[p].setAttributes(styles, true);
56678                     }
56679                 }
56680                 if (!display) {
56681                     for (p in label) {
56682                         if (label[p]) {
56683                             if (label[p].setAttributes) {
56684                                 label[p].setAttributes({
56685                                     hidden: true
56686                                 }, true);
56687                             } else if(label[p].setVisible) {
56688                                 label[p].setVisible(false);
56689                             }
56690                         }
56691                     }
56692                 }
56693                 config.renderer(label, storeItem);
56694                 me.onPlaceCallout(label, storeItem, item, i, display, animate,
56695                                   j, count, previouslyPlacedCallouts);
56696                 previouslyPlacedCallouts.push(label);
56697                 count++;
56698             }
56699         }
56700         this.hideCallouts(count);
56701     },
56702
56703     onCreateCallout: function(storeItem, item, i, display) {
56704         var me = this,
56705             group = me.calloutsGroup,
56706             config = me.callouts,
56707             styles = config.styles,
56708             width = styles.width,
56709             height = styles.height,
56710             chart = me.chart,
56711             surface = chart.surface,
56712             calloutObj = {
56713                 //label: false,
56714                 //box: false,
56715                 lines: false
56716             };
56717
56718         calloutObj.lines = surface.add(Ext.apply({},
56719         {
56720             type: 'path',
56721             path: 'M0,0',
56722             stroke: me.getLegendColor() || '#555'
56723         },
56724         styles));
56725
56726         if (config.items) {
56727             calloutObj.panel = Ext.create('widget.panel', {
56728                 style: "position: absolute;",    
56729                 width: width,
56730                 height: height,
56731                 items: config.items,
56732                 renderTo: chart.el
56733             });
56734         }
56735
56736         return calloutObj;
56737     },
56738
56739     hideCallouts: function(index) {
56740         var calloutsArray = this.calloutsArray,
56741             len = calloutsArray.length,
56742             co,
56743             p;
56744         while (len-->index) {
56745             co = calloutsArray[len];
56746             for (p in co) {
56747                 if (co[p]) {
56748                     co[p].hide(true);
56749                 }
56750             }
56751         }
56752     }
56753 });
56754
56755 /**
56756  * @class Ext.draw.CompositeSprite
56757  * @extends Ext.util.MixedCollection
56758  *
56759  * A composite Sprite handles a group of sprites with common methods to a sprite
56760  * such as `hide`, `show`, `setAttributes`. These methods are applied to the set of sprites
56761  * added to the group.
56762  *
56763  * CompositeSprite extends {@link Ext.util.MixedCollection} so you can use the same methods
56764  * in `MixedCollection` to iterate through sprites, add and remove elements, etc.
56765  *
56766  * In order to create a CompositeSprite, one has to provide a handle to the surface where it is
56767  * rendered:
56768  *
56769  *     var group = Ext.create('Ext.draw.CompositeSprite', {
56770  *         surface: drawComponent.surface
56771  *     });
56772  *                  
56773  * Then just by using `MixedCollection` methods it's possible to add {@link Ext.draw.Sprite}s:
56774  *  
56775  *     group.add(sprite1);
56776  *     group.add(sprite2);
56777  *     group.add(sprite3);
56778  *                  
56779  * And then apply common Sprite methods to them:
56780  *  
56781  *     group.setAttributes({
56782  *         fill: '#f00'
56783  *     }, true);
56784  */
56785 Ext.define('Ext.draw.CompositeSprite', {
56786
56787     /* Begin Definitions */
56788
56789     extend: 'Ext.util.MixedCollection',
56790     mixins: {
56791         animate: 'Ext.util.Animate'
56792     },
56793
56794     /* End Definitions */
56795     isCompositeSprite: true,
56796     constructor: function(config) {
56797         var me = this;
56798         
56799         config = config || {};
56800         Ext.apply(me, config);
56801
56802         me.addEvents(
56803             'mousedown',
56804             'mouseup',
56805             'mouseover',
56806             'mouseout',
56807             'click'
56808         );
56809         me.id = Ext.id(null, 'ext-sprite-group-');
56810         me.callParent();
56811     },
56812
56813     // @private
56814     onClick: function(e) {
56815         this.fireEvent('click', e);
56816     },
56817
56818     // @private
56819     onMouseUp: function(e) {
56820         this.fireEvent('mouseup', e);
56821     },
56822
56823     // @private
56824     onMouseDown: function(e) {
56825         this.fireEvent('mousedown', e);
56826     },
56827
56828     // @private
56829     onMouseOver: function(e) {
56830         this.fireEvent('mouseover', e);
56831     },
56832
56833     // @private
56834     onMouseOut: function(e) {
56835         this.fireEvent('mouseout', e);
56836     },
56837
56838     attachEvents: function(o) {
56839         var me = this;
56840         
56841         o.on({
56842             scope: me,
56843             mousedown: me.onMouseDown,
56844             mouseup: me.onMouseUp,
56845             mouseover: me.onMouseOver,
56846             mouseout: me.onMouseOut,
56847             click: me.onClick
56848         });
56849     },
56850
56851     /** Add a Sprite to the Group */
56852     add: function(key, o) {
56853         var result = this.callParent(arguments);
56854         this.attachEvents(result);
56855         return result;
56856     },
56857
56858     insert: function(index, key, o) {
56859         return this.callParent(arguments);
56860     },
56861
56862     /** Remove a Sprite from the Group */
56863     remove: function(o) {
56864         var me = this;
56865         
56866         o.un({
56867             scope: me,
56868             mousedown: me.onMouseDown,
56869             mouseup: me.onMouseUp,
56870             mouseover: me.onMouseOver,
56871             mouseout: me.onMouseOut,
56872             click: me.onClick
56873         });
56874         me.callParent(arguments);
56875     },
56876     
56877     /**
56878      * Returns the group bounding box.
56879      * Behaves like {@link Ext.draw.Sprite} getBBox method.
56880     */
56881     getBBox: function() {
56882         var i = 0,
56883             sprite,
56884             bb,
56885             items = this.items,
56886             len = this.length,
56887             infinity = Infinity,
56888             minX = infinity,
56889             maxHeight = -infinity,
56890             minY = infinity,
56891             maxWidth = -infinity,
56892             maxWidthBBox, maxHeightBBox;
56893         
56894         for (; i < len; i++) {
56895             sprite = items[i];
56896             if (sprite.el) {
56897                 bb = sprite.getBBox();
56898                 minX = Math.min(minX, bb.x);
56899                 minY = Math.min(minY, bb.y);
56900                 maxHeight = Math.max(maxHeight, bb.height + bb.y);
56901                 maxWidth = Math.max(maxWidth, bb.width + bb.x);
56902             }
56903         }
56904         
56905         return {
56906             x: minX,
56907             y: minY,
56908             height: maxHeight - minY,
56909             width: maxWidth - minX
56910         };
56911     },
56912
56913     /**
56914      *  Iterates through all sprites calling
56915      *  `setAttributes` on each one. For more information
56916      *  {@link Ext.draw.Sprite} provides a description of the
56917      *  attributes that can be set with this method.
56918      */
56919     setAttributes: function(attrs, redraw) {
56920         var i = 0,
56921             items = this.items,
56922             len = this.length;
56923             
56924         for (; i < len; i++) {
56925             items[i].setAttributes(attrs, redraw);
56926         }
56927         return this;
56928     },
56929
56930     /**
56931      * Hides all sprites. If the first parameter of the method is true
56932      * then a redraw will be forced for each sprite.
56933      */
56934     hide: function(redraw) {
56935         var i = 0,
56936             items = this.items,
56937             len = this.length;
56938             
56939         for (; i < len; i++) {
56940             items[i].hide(redraw);
56941         }
56942         return this;
56943     },
56944
56945     /**
56946      * Shows all sprites. If the first parameter of the method is true
56947      * then a redraw will be forced for each sprite.
56948      */
56949     show: function(redraw) {
56950         var i = 0,
56951             items = this.items,
56952             len = this.length;
56953             
56954         for (; i < len; i++) {
56955             items[i].show(redraw);
56956         }
56957         return this;
56958     },
56959
56960     redraw: function() {
56961         var me = this,
56962             i = 0,
56963             items = me.items,
56964             surface = me.getSurface(),
56965             len = me.length;
56966         
56967         if (surface) {
56968             for (; i < len; i++) {
56969                 surface.renderItem(items[i]);
56970             }
56971         }
56972         return me;
56973     },
56974
56975     setStyle: function(obj) {
56976         var i = 0,
56977             items = this.items,
56978             len = this.length,
56979             item, el;
56980             
56981         for (; i < len; i++) {
56982             item = items[i];
56983             el = item.el;
56984             if (el) {
56985                 el.setStyle(obj);
56986             }
56987         }
56988     },
56989
56990     addCls: function(obj) {
56991         var i = 0,
56992             items = this.items,
56993             surface = this.getSurface(),
56994             len = this.length;
56995         
56996         if (surface) {
56997             for (; i < len; i++) {
56998                 surface.addCls(items[i], obj);
56999             }
57000         }
57001     },
57002
57003     removeCls: function(obj) {
57004         var i = 0,
57005             items = this.items,
57006             surface = this.getSurface(),
57007             len = this.length;
57008         
57009         if (surface) {
57010             for (; i < len; i++) {
57011                 surface.removeCls(items[i], obj);
57012             }
57013         }
57014     },
57015     
57016     /**
57017      * Grab the surface from the items
57018      * @private
57019      * @return {Ext.draw.Surface} The surface, null if not found
57020      */
57021     getSurface: function(){
57022         var first = this.first();
57023         if (first) {
57024             return first.surface;
57025         }
57026         return null;
57027     },
57028     
57029     /**
57030      * Destroys the SpriteGroup
57031      */
57032     destroy: function(){
57033         var me = this,
57034             surface = me.getSurface(),
57035             item;
57036             
57037         if (surface) {
57038             while (me.getCount() > 0) {
57039                 item = me.first();
57040                 me.remove(item);
57041                 surface.remove(item);
57042             }
57043         }
57044         me.clearListeners();
57045     }
57046 });
57047
57048 /**
57049  * @class Ext.layout.component.Draw
57050  * @extends Ext.layout.component.Component
57051  * @private
57052  *
57053  */
57054
57055 Ext.define('Ext.layout.component.Draw', {
57056
57057     /* Begin Definitions */
57058
57059     alias: 'layout.draw',
57060
57061     extend: 'Ext.layout.component.Auto',
57062
57063     /* End Definitions */
57064
57065     type: 'draw',
57066
57067     onLayout : function(width, height) {
57068         this.owner.surface.setSize(width, height);
57069         this.callParent(arguments);
57070     }
57071 });
57072 /**
57073  * @class Ext.chart.theme.Theme
57074  * @ignore
57075  */
57076 Ext.define('Ext.chart.theme.Theme', {
57077
57078     /* Begin Definitions */
57079
57080     requires: ['Ext.draw.Color'],
57081
57082     /* End Definitions */
57083
57084     theme: 'Base',
57085     themeAttrs: false,
57086     
57087     initTheme: function(theme) {
57088         var me = this,
57089             themes = Ext.chart.theme,
57090             key, gradients;
57091         if (theme) {
57092             theme = theme.split(':');
57093             for (key in themes) {
57094                 if (key == theme[0]) {
57095                     gradients = theme[1] == 'gradients';
57096                     me.themeAttrs = new themes[key]({
57097                         useGradients: gradients
57098                     });
57099                     if (gradients) {
57100                         me.gradients = me.themeAttrs.gradients;
57101                     }
57102                     if (me.themeAttrs.background) {
57103                         me.background = me.themeAttrs.background;
57104                     }
57105                     return;
57106                 }
57107             }
57108             Ext.Error.raise('No theme found named "' + theme + '"');
57109         }
57110     }
57111 }, 
57112 // This callback is executed right after when the class is created. This scope refers to the newly created class itself
57113 function() {
57114    /* Theme constructor: takes either a complex object with styles like:
57115   
57116    {
57117         axis: {
57118             fill: '#000',
57119             'stroke-width': 1
57120         },
57121         axisLabelTop: {
57122             fill: '#000',
57123             font: '11px Arial'
57124         },
57125         axisLabelLeft: {
57126             fill: '#000',
57127             font: '11px Arial'
57128         },
57129         axisLabelRight: {
57130             fill: '#000',
57131             font: '11px Arial'
57132         },
57133         axisLabelBottom: {
57134             fill: '#000',
57135             font: '11px Arial'
57136         },
57137         axisTitleTop: {
57138             fill: '#000',
57139             font: '11px Arial'
57140         },
57141         axisTitleLeft: {
57142             fill: '#000',
57143             font: '11px Arial'
57144         },
57145         axisTitleRight: {
57146             fill: '#000',
57147             font: '11px Arial'
57148         },
57149         axisTitleBottom: {
57150             fill: '#000',
57151             font: '11px Arial'
57152         },
57153         series: {
57154             'stroke-width': 1
57155         },
57156         seriesLabel: {
57157             font: '12px Arial',
57158             fill: '#333'
57159         },
57160         marker: {
57161             stroke: '#555',
57162             fill: '#000',
57163             radius: 3,
57164             size: 3
57165         },
57166         seriesThemes: [{
57167             fill: '#C6DBEF'
57168         }, {
57169             fill: '#9ECAE1'
57170         }, {
57171             fill: '#6BAED6'
57172         }, {
57173             fill: '#4292C6'
57174         }, {
57175             fill: '#2171B5'
57176         }, {
57177             fill: '#084594'
57178         }],
57179         markerThemes: [{
57180             fill: '#084594',
57181             type: 'circle' 
57182         }, {
57183             fill: '#2171B5',
57184             type: 'cross'
57185         }, {
57186             fill: '#4292C6',
57187             type: 'plus'
57188         }]
57189     }
57190   
57191   ...or also takes just an array of colors and creates the complex object:
57192   
57193   {
57194       colors: ['#aaa', '#bcd', '#eee']
57195   }
57196   
57197   ...or takes just a base color and makes a theme from it
57198   
57199   {
57200       baseColor: '#bce'
57201   }
57202   
57203   To create a new theme you may add it to the Themes object:
57204   
57205   Ext.chart.theme.MyNewTheme = Ext.extend(Object, {
57206       constructor: function(config) {
57207           Ext.chart.theme.call(this, config, {
57208               baseColor: '#mybasecolor'
57209           });
57210       }
57211   });
57212   
57213   //Proposal:
57214   Ext.chart.theme.MyNewTheme = Ext.chart.createTheme('#basecolor');
57215   
57216   ...and then to use it provide the name of the theme (as a lower case string) in the chart config.
57217   
57218   {
57219       theme: 'mynewtheme'
57220   }
57221  */
57222
57223 (function() {
57224     Ext.chart.theme = function(config, base) {
57225         config = config || {};
57226         var i = 0, l, colors, color,
57227             seriesThemes, markerThemes,
57228             seriesTheme, markerTheme, 
57229             key, gradients = [],
57230             midColor, midL;
57231         
57232         if (config.baseColor) {
57233             midColor = Ext.draw.Color.fromString(config.baseColor);
57234             midL = midColor.getHSL()[2];
57235             if (midL < 0.15) {
57236                 midColor = midColor.getLighter(0.3);
57237             } else if (midL < 0.3) {
57238                 midColor = midColor.getLighter(0.15);
57239             } else if (midL > 0.85) {
57240                 midColor = midColor.getDarker(0.3);
57241             } else if (midL > 0.7) {
57242                 midColor = midColor.getDarker(0.15);
57243             }
57244             config.colors = [ midColor.getDarker(0.3).toString(),
57245                               midColor.getDarker(0.15).toString(),
57246                               midColor.toString(),
57247                               midColor.getLighter(0.15).toString(),
57248                               midColor.getLighter(0.3).toString()];
57249
57250             delete config.baseColor;
57251         }
57252         if (config.colors) {
57253             colors = config.colors.slice();
57254             markerThemes = base.markerThemes;
57255             seriesThemes = base.seriesThemes;
57256             l = colors.length;
57257             base.colors = colors;
57258             for (; i < l; i++) {
57259                 color = colors[i];
57260                 markerTheme = markerThemes[i] || {};
57261                 seriesTheme = seriesThemes[i] || {};
57262                 markerTheme.fill = seriesTheme.fill = markerTheme.stroke = seriesTheme.stroke = color;
57263                 markerThemes[i] = markerTheme;
57264                 seriesThemes[i] = seriesTheme;
57265             }
57266             base.markerThemes = markerThemes.slice(0, l);
57267             base.seriesThemes = seriesThemes.slice(0, l);
57268         //the user is configuring something in particular (either markers, series or pie slices)
57269         }
57270         for (key in base) {
57271             if (key in config) {
57272                 if (Ext.isObject(config[key]) && Ext.isObject(base[key])) {
57273                     Ext.apply(base[key], config[key]);
57274                 } else {
57275                     base[key] = config[key];
57276                 }
57277             }
57278         }
57279         if (config.useGradients) {
57280             colors = base.colors || (function () {
57281                 var ans = [];
57282                 for (i = 0, seriesThemes = base.seriesThemes, l = seriesThemes.length; i < l; i++) {
57283                     ans.push(seriesThemes[i].fill || seriesThemes[i].stroke);
57284                 }
57285                 return ans;
57286             })();
57287             for (i = 0, l = colors.length; i < l; i++) {
57288                 midColor = Ext.draw.Color.fromString(colors[i]);
57289                 if (midColor) {
57290                     color = midColor.getDarker(0.1).toString();
57291                     midColor = midColor.toString();
57292                     key = 'theme-' + midColor.substr(1) + '-' + color.substr(1);
57293                     gradients.push({
57294                         id: key,
57295                         angle: 45,
57296                         stops: {
57297                             0: {
57298                                 color: midColor.toString()
57299                             },
57300                             100: {
57301                                 color: color.toString()
57302                             }
57303                         }
57304                     });
57305                     colors[i] = 'url(#' + key + ')'; 
57306                 }
57307             }
57308             base.gradients = gradients;
57309             base.colors = colors;
57310         }
57311         /*
57312         base.axis = Ext.apply(base.axis || {}, config.axis || {});
57313         base.axisLabel = Ext.apply(base.axisLabel || {}, config.axisLabel || {});
57314         base.axisTitle = Ext.apply(base.axisTitle || {}, config.axisTitle || {});
57315         */
57316         Ext.apply(this, base);
57317     };
57318 })();
57319 });
57320
57321 /**
57322  * @class Ext.chart.Mask
57323  *
57324  * Defines a mask for a chart's series.
57325  * The 'chart' member must be set prior to rendering.
57326  *
57327  * A Mask can be used to select a certain region in a chart.
57328  * When enabled, the `select` event will be triggered when a
57329  * region is selected by the mask, allowing the user to perform
57330  * other tasks like zooming on that region, etc.
57331  *
57332  * In order to use the mask one has to set the Chart `mask` option to
57333  * `true`, `vertical` or `horizontal`. Then a possible configuration for the
57334  * listener could be:
57335  *
57336         items: {
57337             xtype: 'chart',
57338             animate: true,
57339             store: store1,
57340             mask: 'horizontal',
57341             listeners: {
57342                 select: {
57343                     fn: function(me, selection) {
57344                         me.setZoom(selection);
57345                         me.mask.hide();
57346                     }
57347                 }
57348             },
57349
57350  * In this example we zoom the chart to that particular region. You can also get
57351  * a handle to a mask instance from the chart object. The `chart.mask` element is a
57352  * `Ext.Panel`.
57353  * 
57354  * @constructor
57355  */
57356 Ext.define('Ext.chart.Mask', {
57357     constructor: function(config) {
57358         var me = this;
57359
57360         me.addEvents('select');
57361
57362         if (config) {
57363             Ext.apply(me, config);
57364         }
57365         if (me.mask) {
57366             me.on('afterrender', function() {
57367                 //create a mask layer component
57368                 var comp = Ext.create('Ext.chart.MaskLayer', {
57369                     renderTo: me.el
57370                 });
57371                 comp.el.on({
57372                     'mousemove': function(e) {
57373                         me.onMouseMove(e);
57374                     },
57375                     'mouseup': function(e) {
57376                         me.resized(e);
57377                     }
57378                 });
57379                 //create a resize handler for the component
57380                 var resizeHandler = Ext.create('Ext.resizer.Resizer', {
57381                     el: comp.el,
57382                     handles: 'all',
57383                     pinned: true
57384                 });
57385                 resizeHandler.on({
57386                     'resize': function(e) {
57387                         me.resized(e);    
57388                     }    
57389                 });
57390                 comp.initDraggable();
57391                 me.maskType = me.mask;
57392                 me.mask = comp;
57393                 me.maskSprite = me.surface.add({
57394                     type: 'path',
57395                     path: ['M', 0, 0],
57396                     zIndex: 1001,
57397                     opacity: 0.7,
57398                     hidden: true,
57399                     stroke: '#444'
57400                 });
57401             }, me, { single: true });
57402         }
57403     },
57404     
57405     resized: function(e) {
57406         var me = this,
57407             bbox = me.bbox || me.chartBBox,
57408             x = bbox.x,
57409             y = bbox.y,
57410             width = bbox.width,
57411             height = bbox.height,
57412             box = me.mask.getBox(true),
57413             max = Math.max,
57414             min = Math.min,
57415             staticX = box.x - x,
57416             staticY = box.y - y;
57417         
57418         staticX = max(staticX, x);
57419         staticY = max(staticY, y);
57420         staticX = min(staticX, width);
57421         staticY = min(staticY, height);
57422         box.x = staticX;
57423         box.y = staticY;
57424         me.fireEvent('select', me, box);
57425     },
57426
57427     onMouseUp: function(e) {
57428         var me = this,
57429             bbox = me.bbox || me.chartBBox,
57430             sel = me.maskSelection;
57431         me.maskMouseDown = false;
57432         me.mouseDown = false;
57433         if (me.mouseMoved) {
57434             me.onMouseMove(e);
57435             me.mouseMoved = false;
57436             me.fireEvent('select', me, {
57437                 x: sel.x - bbox.x,
57438                 y: sel.y - bbox.y,
57439                 width: sel.width,
57440                 height: sel.height
57441             });
57442         }
57443     },
57444
57445     onMouseDown: function(e) {
57446         var me = this;
57447         me.mouseDown = true;
57448         me.mouseMoved = false;
57449         me.maskMouseDown = {
57450             x: e.getPageX() - me.el.getX(),
57451             y: e.getPageY() - me.el.getY()
57452         };
57453     },
57454
57455     onMouseMove: function(e) {
57456         var me = this,
57457             mask = me.maskType,
57458             bbox = me.bbox || me.chartBBox,
57459             x = bbox.x,
57460             y = bbox.y,
57461             math = Math,
57462             floor = math.floor,
57463             abs = math.abs,
57464             min = math.min,
57465             max = math.max,
57466             height = floor(y + bbox.height),
57467             width = floor(x + bbox.width),
57468             posX = e.getPageX(),
57469             posY = e.getPageY(),
57470             staticX = posX - me.el.getX(),
57471             staticY = posY - me.el.getY(),
57472             maskMouseDown = me.maskMouseDown,
57473             path;
57474         
57475         me.mouseMoved = me.mouseDown;
57476         staticX = max(staticX, x);
57477         staticY = max(staticY, y);
57478         staticX = min(staticX, width);
57479         staticY = min(staticY, height);
57480         if (maskMouseDown && me.mouseDown) {
57481             if (mask == 'horizontal') {
57482                 staticY = y;
57483                 maskMouseDown.y = height;
57484                 posY = me.el.getY() + bbox.height + me.insetPadding;
57485             }
57486             else if (mask == 'vertical') {
57487                 staticX = x;
57488                 maskMouseDown.x = width;
57489             }
57490             width = maskMouseDown.x - staticX;
57491             height = maskMouseDown.y - staticY;
57492             path = ['M', staticX, staticY, 'l', width, 0, 0, height, -width, 0, 'z'];
57493             me.maskSelection = {
57494                 x: width > 0 ? staticX : staticX + width,
57495                 y: height > 0 ? staticY : staticY + height,
57496                 width: abs(width),
57497                 height: abs(height)
57498             };
57499             me.mask.updateBox({
57500                 x: posX - abs(width),
57501                 y: posY - abs(height),
57502                 width: abs(width),
57503                 height: abs(height)
57504             });
57505             me.mask.show();
57506             me.maskSprite.setAttributes({
57507                 hidden: true    
57508             }, true);
57509         }
57510         else {
57511             if (mask == 'horizontal') {
57512                 path = ['M', staticX, y, 'L', staticX, height];
57513             }
57514             else if (mask == 'vertical') {
57515                 path = ['M', x, staticY, 'L', width, staticY];
57516             }
57517             else {
57518                 path = ['M', staticX, y, 'L', staticX, height, 'M', x, staticY, 'L', width, staticY];
57519             }
57520             me.maskSprite.setAttributes({
57521                 path: path,
57522                 fill: me.maskMouseDown ? me.maskSprite.stroke : false,
57523                 'stroke-width': mask === true ? 1 : 3,
57524                 hidden: false
57525             }, true);
57526         }
57527     },
57528
57529     onMouseLeave: function(e) {
57530         var me = this;
57531         me.mouseMoved = false;
57532         me.mouseDown = false;
57533         me.maskMouseDown = false;
57534         me.mask.hide();
57535         me.maskSprite.hide(true);
57536     }
57537 });
57538     
57539 /**
57540  * @class Ext.chart.Navigation
57541  *
57542  * Handles panning and zooming capabilities.
57543  * 
57544  * @ignore
57545  */
57546 Ext.define('Ext.chart.Navigation', {
57547
57548     constructor: function() {
57549         this.originalStore = this.store;
57550     },
57551     
57552     //filters the store to the specified interval(s)
57553     setZoom: function(zoomConfig) {
57554         var me = this,
57555             store = me.substore || me.store,
57556             bbox = me.chartBBox,
57557             len = store.getCount(),
57558             from = (zoomConfig.x / bbox.width * len) >> 0,
57559             to = Math.ceil(((zoomConfig.x + zoomConfig.width) / bbox.width * len)),
57560             recFieldsLen, recFields = [], curField, json = [], obj;
57561         
57562         store.each(function(rec, i) {
57563             if (i < from || i > to) {
57564                 return;
57565             }
57566             obj = {};
57567             //get all record field names in a simple array
57568             if (!recFields.length) {
57569                 rec.fields.each(function(f) {
57570                     recFields.push(f.name);
57571                 });
57572                 recFieldsLen = recFields.length;
57573             }
57574             //append record values to an aggregation record
57575             for (i = 0; i < recFieldsLen; i++) {
57576                 curField = recFields[i];
57577                 obj[curField] = rec.get(curField);
57578             }
57579             json.push(obj);
57580         });
57581         me.store = me.substore = Ext.create('Ext.data.JsonStore', {
57582             fields: recFields,
57583             data: json
57584         });
57585         me.redraw(true);
57586     },
57587
57588     restoreZoom: function() {
57589         this.store = this.substore = this.originalStore;
57590         this.redraw(true);
57591     }
57592     
57593 });
57594 /**
57595  * @class Ext.chart.Shape
57596  * @ignore
57597  */
57598 Ext.define('Ext.chart.Shape', {
57599
57600     /* Begin Definitions */
57601
57602     singleton: true,
57603
57604     /* End Definitions */
57605
57606     circle: function (surface, opts) {
57607         return surface.add(Ext.apply({
57608             type: 'circle',
57609             x: opts.x,
57610             y: opts.y,
57611             stroke: null,
57612             radius: opts.radius
57613         }, opts));
57614     },
57615     line: function (surface, opts) {
57616         return surface.add(Ext.apply({
57617             type: 'rect',
57618             x: opts.x - opts.radius,
57619             y: opts.y - opts.radius,
57620             height: 2 * opts.radius,
57621             width: 2 * opts.radius / 5
57622         }, opts));
57623     },
57624     square: function (surface, opts) {
57625         return surface.add(Ext.applyIf({
57626             type: 'rect',
57627             x: opts.x - opts.radius,
57628             y: opts.y - opts.radius,
57629             height: 2 * opts.radius,
57630             width: 2 * opts.radius,
57631             radius: null
57632         }, opts));
57633     },
57634     triangle: function (surface, opts) {
57635         opts.radius *= 1.75;
57636         return surface.add(Ext.apply({
57637             type: 'path',
57638             stroke: null,
57639             path: "M".concat(opts.x, ",", opts.y, "m0-", opts.radius * 0.58, "l", opts.radius * 0.5, ",", opts.radius * 0.87, "-", opts.radius, ",0z")
57640         }, opts));
57641     },
57642     diamond: function (surface, opts) {
57643         var r = opts.radius;
57644         r *= 1.5;
57645         return surface.add(Ext.apply({
57646             type: 'path',
57647             stroke: null,
57648             path: ["M", opts.x, opts.y - r, "l", r, r, -r, r, -r, -r, r, -r, "z"]
57649         }, opts));
57650     },
57651     cross: function (surface, opts) {
57652         var r = opts.radius;
57653         r = r / 1.7;
57654         return surface.add(Ext.apply({
57655             type: 'path',
57656             stroke: null,
57657             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"])
57658         }, opts));
57659     },
57660     plus: function (surface, opts) {
57661         var r = opts.radius / 1.3;
57662         return surface.add(Ext.apply({
57663             type: 'path',
57664             stroke: null,
57665             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"])
57666         }, opts));
57667     },
57668     arrow: function (surface, opts) {
57669         var r = opts.radius;
57670         return surface.add(Ext.apply({
57671             type: 'path',
57672             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")
57673         }, opts));
57674     },
57675     drop: function (surface, x, y, text, size, angle) {
57676         size = size || 30;
57677         angle = angle || 0;
57678         surface.add({
57679             type: 'path',
57680             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'],
57681             fill: '#000',
57682             stroke: 'none',
57683             rotate: {
57684                 degrees: 22.5 - angle,
57685                 x: x,
57686                 y: y
57687             }
57688         });
57689         angle = (angle + 90) * Math.PI / 180;
57690         surface.add({
57691             type: 'text',
57692             x: x + size * Math.sin(angle) - 10, // Shift here, Not sure why.
57693             y: y + size * Math.cos(angle) + 5,
57694             text:  text,
57695             'font-size': size * 12 / 40,
57696             stroke: 'none',
57697             fill: '#fff'
57698         });
57699     }
57700 });
57701 /**
57702  * @class Ext.draw.Surface
57703  * @extends Object
57704  *
57705  * A Surface is an interface to render methods inside a draw {@link Ext.draw.Component}.
57706  * A Surface contains methods to render sprites, get bounding boxes of sprites, add
57707  * sprites to the canvas, initialize other graphic components, etc. One of the most used
57708  * methods for this class is the `add` method, to add Sprites to the surface.
57709  *
57710  * Most of the Surface methods are abstract and they have a concrete implementation
57711  * in VML or SVG engines.
57712  *
57713  * A Surface instance can be accessed as a property of a draw component. For example:
57714  *
57715  *     drawComponent.surface.add({
57716  *         type: 'circle',
57717  *         fill: '#ffc',
57718  *         radius: 100,
57719  *         x: 100,
57720  *         y: 100
57721  *     });
57722  *
57723  * The configuration object passed in the `add` method is the same as described in the {@link Ext.draw.Sprite}
57724  * class documentation.
57725  *
57726  * ### Listeners
57727  *
57728  * You can also add event listeners to the surface using the `Observable` listener syntax. Supported events are:
57729  *
57730  * - mousedown
57731  * - mouseup
57732  * - mouseover
57733  * - mouseout
57734  * - mousemove
57735  * - mouseenter
57736  * - mouseleave
57737  * - click
57738  *
57739  * For example:
57740  *
57741  *     drawComponent.surface.on({
57742  *        'mousemove': function() {
57743  *             console.log('moving the mouse over the surface');   
57744  *         }
57745  *     });
57746  *
57747  * ## Example
57748  *
57749  *     drawComponent.surface.add([
57750  *         {
57751  *             type: 'circle',
57752  *             radius: 10,
57753  *             fill: '#f00',
57754  *             x: 10,
57755  *             y: 10,
57756  *             group: 'circles'
57757  *         },
57758  *         {
57759  *             type: 'circle',
57760  *             radius: 10,
57761  *             fill: '#0f0',
57762  *             x: 50,
57763  *             y: 50,
57764  *             group: 'circles'
57765  *         },
57766  *         {
57767  *             type: 'circle',
57768  *             radius: 10,
57769  *             fill: '#00f',
57770  *             x: 100,
57771  *             y: 100,
57772  *             group: 'circles'
57773  *         },
57774  *         {
57775  *             type: 'rect',
57776  *             radius: 10,
57777  *             x: 10,
57778  *             y: 10,
57779  *             group: 'rectangles'
57780  *         },
57781  *         {
57782  *             type: 'rect',
57783  *             radius: 10,
57784  *             x: 50,
57785  *             y: 50,
57786  *             group: 'rectangles'
57787  *         },
57788  *         {
57789  *             type: 'rect',
57790  *             radius: 10,
57791  *             x: 100,
57792  *             y: 100,
57793  *             group: 'rectangles'
57794  *         }
57795  *     ]);
57796  *     
57797  *     // Get references to my groups
57798  *     my circles = surface.getGroup('circles');
57799  *     my rectangles = surface.getGroup('rectangles');
57800  *     
57801  *     // Animate the circles down
57802  *     circles.animate({
57803  *         duration: 1000,
57804  *         translate: {
57805  *             y: 200
57806  *         }
57807  *     });
57808  *     
57809  *     // Animate the rectangles across
57810  *     rectangles.animate({
57811  *         duration: 1000,
57812  *         translate: {
57813  *             x: 200
57814  *         }
57815  *     });
57816  */
57817 Ext.define('Ext.draw.Surface', {
57818
57819     /* Begin Definitions */
57820
57821     mixins: {
57822         observable: 'Ext.util.Observable'
57823     },
57824
57825     requires: ['Ext.draw.CompositeSprite'],
57826     uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml'],
57827
57828     separatorRe: /[, ]+/,
57829
57830     statics: {
57831         /**
57832          * Create and return a new concrete Surface instance appropriate for the current environment.
57833          * @param {Object} config Initial configuration for the Surface instance
57834          * @param {Array} enginePriority Optional order of implementations to use; the first one that is
57835          *                available in the current environment will be used. Defaults to
57836          *                <code>['Svg', 'Vml']</code>.
57837          */
57838         create: function(config, enginePriority) {
57839             enginePriority = enginePriority || ['Svg', 'Vml'];
57840
57841             var i = 0,
57842                 len = enginePriority.length,
57843                 surfaceClass;
57844
57845             for (; i < len; i++) {
57846                 if (Ext.supports[enginePriority[i]]) {
57847                     return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
57848                 }
57849             }
57850             return false;
57851         }
57852     },
57853
57854     /* End Definitions */
57855
57856     // @private
57857     availableAttrs: {
57858         blur: 0,
57859         "clip-rect": "0 0 1e9 1e9",
57860         cursor: "default",
57861         cx: 0,
57862         cy: 0,
57863         'dominant-baseline': 'auto',
57864         fill: "none",
57865         "fill-opacity": 1,
57866         font: '10px "Arial"',
57867         "font-family": '"Arial"',
57868         "font-size": "10",
57869         "font-style": "normal",
57870         "font-weight": 400,
57871         gradient: "",
57872         height: 0,
57873         hidden: false,
57874         href: "http://sencha.com/",
57875         opacity: 1,
57876         path: "M0,0",
57877         radius: 0,
57878         rx: 0,
57879         ry: 0,
57880         scale: "1 1",
57881         src: "",
57882         stroke: "#000",
57883         "stroke-dasharray": "",
57884         "stroke-linecap": "butt",
57885         "stroke-linejoin": "butt",
57886         "stroke-miterlimit": 0,
57887         "stroke-opacity": 1,
57888         "stroke-width": 1,
57889         target: "_blank",
57890         text: "",
57891         "text-anchor": "middle",
57892         title: "Ext Draw",
57893         width: 0,
57894         x: 0,
57895         y: 0,
57896         zIndex: 0
57897     },
57898
57899  /**
57900   * @cfg {Number} height
57901   * The height of this component in pixels (defaults to auto).
57902   * <b>Note</b> to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
57903   */
57904  /**
57905   * @cfg {Number} width
57906   * The width of this component in pixels (defaults to auto).
57907   * <b>Note</b> to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
57908   */
57909     container: undefined,
57910     height: 352,
57911     width: 512,
57912     x: 0,
57913     y: 0,
57914
57915     constructor: function(config) {
57916         var me = this;
57917         config = config || {};
57918         Ext.apply(me, config);
57919
57920         me.domRef = Ext.getDoc().dom;
57921         
57922         me.customAttributes = {};
57923
57924         me.addEvents(
57925             'mousedown',
57926             'mouseup',
57927             'mouseover',
57928             'mouseout',
57929             'mousemove',
57930             'mouseenter',
57931             'mouseleave',
57932             'click'
57933         );
57934
57935         me.mixins.observable.constructor.call(me);
57936
57937         me.getId();
57938         me.initGradients();
57939         me.initItems();
57940         if (me.renderTo) {
57941             me.render(me.renderTo);
57942             delete me.renderTo;
57943         }
57944         me.initBackground(config.background);
57945     },
57946
57947     // @private called to initialize components in the surface
57948     // this is dependent on the underlying implementation.
57949     initSurface: Ext.emptyFn,
57950
57951     // @private called to setup the surface to render an item
57952     //this is dependent on the underlying implementation.
57953     renderItem: Ext.emptyFn,
57954
57955     // @private
57956     renderItems: Ext.emptyFn,
57957
57958     // @private
57959     setViewBox: Ext.emptyFn,
57960
57961     /**
57962      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
57963      *
57964      * For example:
57965      *
57966      *          drawComponent.surface.addCls(sprite, 'x-visible');
57967      *      
57968      * @param {Object} sprite The sprite to add the class to.
57969      * @param {String/Array} className The CSS class to add, or an array of classes
57970      * @method
57971      */
57972     addCls: Ext.emptyFn,
57973
57974     /**
57975      * Removes one or more CSS classes from the element.
57976      *
57977      * For example:
57978      *
57979      *      drawComponent.surface.removeCls(sprite, 'x-visible');
57980      *      
57981      * @param {Object} sprite The sprite to remove the class from.
57982      * @param {String/Array} className The CSS class to remove, or an array of classes
57983      * @method
57984      */
57985     removeCls: Ext.emptyFn,
57986
57987     /**
57988      * Sets CSS style attributes to an element.
57989      *
57990      * For example:
57991      *
57992      *      drawComponent.surface.setStyle(sprite, {
57993      *          'cursor': 'pointer'
57994      *      });
57995      *      
57996      * @param {Object} sprite The sprite to add, or an array of classes to
57997      * @param {Object} styles An Object with CSS styles.
57998      * @method
57999      */
58000     setStyle: Ext.emptyFn,
58001
58002     // @private
58003     initGradients: function() {
58004         var gradients = this.gradients;
58005         if (gradients) {
58006             Ext.each(gradients, this.addGradient, this);
58007         }
58008     },
58009
58010     // @private
58011     initItems: function() {
58012         var items = this.items;
58013         this.items = Ext.create('Ext.draw.CompositeSprite');
58014         this.groups = Ext.create('Ext.draw.CompositeSprite');
58015         if (items) {
58016             this.add(items);
58017         }
58018     },
58019     
58020     // @private
58021     initBackground: function(config) {
58022         var me = this,
58023             width = me.width,
58024             height = me.height,
58025             gradientId, gradient, backgroundSprite;
58026         if (config) {
58027             if (config.gradient) {
58028                 gradient = config.gradient;
58029                 gradientId = gradient.id;
58030                 me.addGradient(gradient);
58031                 me.background = me.add({
58032                     type: 'rect',
58033                     x: 0,
58034                     y: 0,
58035                     width: width,
58036                     height: height,
58037                     fill: 'url(#' + gradientId + ')'
58038                 });
58039             } else if (config.fill) {
58040                 me.background = me.add({
58041                     type: 'rect',
58042                     x: 0,
58043                     y: 0,
58044                     width: width,
58045                     height: height,
58046                     fill: config.fill
58047                 });
58048             } else if (config.image) {
58049                 me.background = me.add({
58050                     type: 'image',
58051                     x: 0,
58052                     y: 0,
58053                     width: width,
58054                     height: height,
58055                     src: config.image
58056                 });
58057             }
58058         }
58059     },
58060     
58061     /**
58062      * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
58063      *
58064      * For example:
58065      *
58066      *      drawComponent.surface.setSize(500, 500);
58067      *
58068      * This method is generally called when also setting the size of the draw Component.
58069      * 
58070      * @param {Number} w The new width of the canvas.
58071      * @param {Number} h The new height of the canvas.
58072      */
58073     setSize: function(w, h) {
58074         if (this.background) {
58075             this.background.setAttributes({
58076                 width: w,
58077                 height: h,
58078                 hidden: false
58079             }, true);
58080         }
58081     },
58082
58083     // @private
58084     scrubAttrs: function(sprite) {
58085         var i,
58086             attrs = {},
58087             exclude = {},
58088             sattr = sprite.attr;
58089         for (i in sattr) {    
58090             // Narrow down attributes to the main set
58091             if (this.translateAttrs.hasOwnProperty(i)) {
58092                 // Translated attr
58093                 attrs[this.translateAttrs[i]] = sattr[i];
58094                 exclude[this.translateAttrs[i]] = true;
58095             }
58096             else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) {
58097                 // Passtrhough attr
58098                 attrs[i] = sattr[i];
58099             }
58100         }
58101         return attrs;
58102     },
58103
58104     // @private
58105     onClick: function(e) {
58106         this.processEvent('click', e);
58107     },
58108
58109     // @private
58110     onMouseUp: function(e) {
58111         this.processEvent('mouseup', e);
58112     },
58113
58114     // @private
58115     onMouseDown: function(e) {
58116         this.processEvent('mousedown', e);
58117     },
58118
58119     // @private
58120     onMouseOver: function(e) {
58121         this.processEvent('mouseover', e);
58122     },
58123
58124     // @private
58125     onMouseOut: function(e) {
58126         this.processEvent('mouseout', e);
58127     },
58128
58129     // @private
58130     onMouseMove: function(e) {
58131         this.fireEvent('mousemove', e);
58132     },
58133
58134     // @private
58135     onMouseEnter: Ext.emptyFn,
58136
58137     // @private
58138     onMouseLeave: Ext.emptyFn,
58139
58140     /**
58141      * Add a gradient definition to the Surface. Note that in some surface engines, adding
58142      * a gradient via this method will not take effect if the surface has already been rendered.
58143      * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
58144      * than calling this method, especially if the surface is rendered immediately (e.g. due to
58145      * 'renderTo' in its config). For more information on how to create gradients in the Chart
58146      * configuration object please refer to {@link Ext.chart.Chart}.
58147      *
58148      * The gradient object to be passed into this method is composed by:
58149      * 
58150      * 
58151      *  - **id** - string - The unique name of the gradient.
58152      *  - **angle** - number, optional - The angle of the gradient in degrees.
58153      *  - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
58154      * 
58155      *
58156      For example:
58157                 drawComponent.surface.addGradient({
58158                     id: 'gradientId',
58159                     angle: 45,
58160                     stops: {
58161                         0: {
58162                             color: '#555'
58163                         },
58164                         100: {
58165                             color: '#ddd'
58166                         }
58167                     }
58168                 });
58169      *
58170      * @method
58171      */
58172     addGradient: Ext.emptyFn,
58173
58174     /**
58175      * Add a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be passed into this method.
58176      *
58177      * For example:
58178      *
58179      *     drawComponent.surface.add({
58180      *         type: 'circle',
58181      *         fill: '#ffc',
58182      *         radius: 100,
58183      *         x: 100,
58184      *         y: 100
58185      *     });
58186      *
58187     */
58188     add: function() {
58189         var args = Array.prototype.slice.call(arguments),
58190             sprite,
58191             index;
58192
58193         var hasMultipleArgs = args.length > 1;
58194         if (hasMultipleArgs || Ext.isArray(args[0])) {
58195             var items = hasMultipleArgs ? args : args[0],
58196                 results = [],
58197                 i, ln, item;
58198
58199             for (i = 0, ln = items.length; i < ln; i++) {
58200                 item = items[i];
58201                 item = this.add(item);
58202                 results.push(item);
58203             }
58204
58205             return results;
58206         }
58207         sprite = this.prepareItems(args[0], true)[0];
58208         this.normalizeSpriteCollection(sprite);
58209         this.onAdd(sprite);
58210         return sprite;
58211     },
58212
58213     /**
58214      * @private
58215      * Insert or move a given sprite into the correct position in the items
58216      * MixedCollection, according to its zIndex. Will be inserted at the end of
58217      * an existing series of sprites with the same or lower zIndex. If the sprite
58218      * is already positioned within an appropriate zIndex group, it will not be moved.
58219      * This ordering can be used by subclasses to assist in rendering the sprites in
58220      * the correct order for proper z-index stacking.
58221      * @param {Ext.draw.Sprite} sprite
58222      * @return {Number} the sprite's new index in the list
58223      */
58224     normalizeSpriteCollection: function(sprite) {
58225         var items = this.items,
58226             zIndex = sprite.attr.zIndex,
58227             idx = items.indexOf(sprite);
58228
58229         if (idx < 0 || (idx > 0 && items.getAt(idx - 1).attr.zIndex > zIndex) ||
58230                 (idx < items.length - 1 && items.getAt(idx + 1).attr.zIndex < zIndex)) {
58231             items.removeAt(idx);
58232             idx = items.findIndexBy(function(otherSprite) {
58233                 return otherSprite.attr.zIndex > zIndex;
58234             });
58235             if (idx < 0) {
58236                 idx = items.length;
58237             }
58238             items.insert(idx, sprite);
58239         }
58240         return idx;
58241     },
58242
58243     onAdd: function(sprite) {
58244         var group = sprite.group,
58245             draggable = sprite.draggable,
58246             groups, ln, i;
58247         if (group) {
58248             groups = [].concat(group);
58249             ln = groups.length;
58250             for (i = 0; i < ln; i++) {
58251                 group = groups[i];
58252                 this.getGroup(group).add(sprite);
58253             }
58254             delete sprite.group;
58255         }
58256         if (draggable) {
58257             sprite.initDraggable();
58258         }
58259     },
58260
58261     /**
58262      * Remove a given sprite from the surface, optionally destroying the sprite in the process.
58263      * You can also call the sprite own `remove` method.
58264      *
58265      * For example:
58266      *
58267      *      drawComponent.surface.remove(sprite);
58268      *      //or...
58269      *      sprite.remove();
58270      *      
58271      * @param {Ext.draw.Sprite} sprite
58272      * @param {Boolean} destroySprite
58273      * @return {Number} the sprite's new index in the list
58274      */
58275     remove: function(sprite, destroySprite) {
58276         if (sprite) {
58277             this.items.remove(sprite);
58278             this.groups.each(function(item) {
58279                 item.remove(sprite);
58280             });
58281             sprite.onRemove();
58282             if (destroySprite === true) {
58283                 sprite.destroy();
58284             }
58285         }
58286     },
58287
58288     /**
58289      * Remove all sprites from the surface, optionally destroying the sprites in the process.
58290      *
58291      * For example:
58292      *
58293      *      drawComponent.surface.removeAll();
58294      *      
58295      * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
58296      * @return {Number} The sprite's new index in the list.
58297      */
58298     removeAll: function(destroySprites) {
58299         var items = this.items.items,
58300             ln = items.length,
58301             i;
58302         for (i = ln - 1; i > -1; i--) {
58303             this.remove(items[i], destroySprites);
58304         }
58305     },
58306
58307     onRemove: Ext.emptyFn,
58308
58309     onDestroy: Ext.emptyFn,
58310
58311     // @private
58312     applyTransformations: function(sprite) {
58313             sprite.bbox.transform = 0;
58314             this.transform(sprite);
58315
58316         var me = this,
58317             dirty = false,
58318             attr = sprite.attr;
58319
58320         if (attr.translation.x != null || attr.translation.y != null) {
58321             me.translate(sprite);
58322             dirty = true;
58323         }
58324         if (attr.scaling.x != null || attr.scaling.y != null) {
58325             me.scale(sprite);
58326             dirty = true;
58327         }
58328         if (attr.rotation.degrees != null) {
58329             me.rotate(sprite);
58330             dirty = true;
58331         }
58332         if (dirty) {
58333             sprite.bbox.transform = 0;
58334             this.transform(sprite);
58335             sprite.transformations = [];
58336         }
58337     },
58338
58339     // @private
58340     rotate: function (sprite) {
58341         var bbox,
58342             deg = sprite.attr.rotation.degrees,
58343             centerX = sprite.attr.rotation.x,
58344             centerY = sprite.attr.rotation.y;
58345         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
58346             bbox = this.getBBox(sprite);
58347             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
58348             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
58349         }
58350         sprite.transformations.push({
58351             type: "rotate",
58352             degrees: deg,
58353             x: centerX,
58354             y: centerY
58355         });
58356     },
58357
58358     // @private
58359     translate: function(sprite) {
58360         var x = sprite.attr.translation.x || 0,
58361             y = sprite.attr.translation.y || 0;
58362         sprite.transformations.push({
58363             type: "translate",
58364             x: x,
58365             y: y
58366         });
58367     },
58368
58369     // @private
58370     scale: function(sprite) {
58371         var bbox,
58372             x = sprite.attr.scaling.x || 1,
58373             y = sprite.attr.scaling.y || 1,
58374             centerX = sprite.attr.scaling.centerX,
58375             centerY = sprite.attr.scaling.centerY;
58376
58377         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
58378             bbox = this.getBBox(sprite);
58379             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
58380             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
58381         }
58382         sprite.transformations.push({
58383             type: "scale",
58384             x: x,
58385             y: y,
58386             centerX: centerX,
58387             centerY: centerY
58388         });
58389     },
58390
58391     // @private
58392     rectPath: function (x, y, w, h, r) {
58393         if (r) {
58394             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"]];
58395         }
58396         return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
58397     },
58398
58399     // @private
58400     ellipsePath: function (x, y, rx, ry) {
58401         if (ry == null) {
58402             ry = rx;
58403         }
58404         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"]];
58405     },
58406
58407     // @private
58408     getPathpath: function (el) {
58409         return el.attr.path;
58410     },
58411
58412     // @private
58413     getPathcircle: function (el) {
58414         var a = el.attr;
58415         return this.ellipsePath(a.x, a.y, a.radius, a.radius);
58416     },
58417
58418     // @private
58419     getPathellipse: function (el) {
58420         var a = el.attr;
58421         return this.ellipsePath(a.x, a.y,
58422                                 a.radiusX || (a.width / 2) || 0,
58423                                 a.radiusY || (a.height / 2) || 0);
58424     },
58425
58426     // @private
58427     getPathrect: function (el) {
58428         var a = el.attr;
58429         return this.rectPath(a.x, a.y, a.width, a.height, a.r);
58430     },
58431
58432     // @private
58433     getPathimage: function (el) {
58434         var a = el.attr;
58435         return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
58436     },
58437
58438     // @private
58439     getPathtext: function (el) {
58440         var bbox = this.getBBoxText(el);
58441         return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
58442     },
58443
58444     createGroup: function(id) {
58445         var group = this.groups.get(id);
58446         if (!group) {
58447             group = Ext.create('Ext.draw.CompositeSprite', {
58448                 surface: this
58449             });
58450             group.id = id || Ext.id(null, 'ext-surface-group-');
58451             this.groups.add(group);
58452         }
58453         return group;
58454     },
58455
58456     /**
58457      * Returns a new group or an existent group associated with the current surface.
58458      * The group returned is a {@link Ext.draw.CompositeSprite} group.
58459      *
58460      * For example:
58461      *
58462      *      var spriteGroup = drawComponent.surface.getGroup('someGroupId');
58463      *      
58464      * @param {String} id The unique identifier of the group.
58465      * @return {Object} The {@link Ext.draw.CompositeSprite}.
58466      */
58467     getGroup: function(id) {
58468         if (typeof id == "string") {
58469             var group = this.groups.get(id);
58470             if (!group) {
58471                 group = this.createGroup(id);
58472             }
58473         } else {
58474             group = id;
58475         }
58476         return group;
58477     },
58478
58479     // @private
58480     prepareItems: function(items, applyDefaults) {
58481         items = [].concat(items);
58482         // Make sure defaults are applied and item is initialized
58483         var item, i, ln;
58484         for (i = 0, ln = items.length; i < ln; i++) {
58485             item = items[i];
58486             if (!(item instanceof Ext.draw.Sprite)) {
58487                 // Temporary, just take in configs...
58488                 item.surface = this;
58489                 items[i] = this.createItem(item);
58490             } else {
58491                 item.surface = this;
58492             }
58493         }
58494         return items;
58495     },
58496     
58497     /**
58498      * Changes the text in the sprite element. The sprite must be a `text` sprite.
58499      * This method can also be called from {@link Ext.draw.Sprite}.
58500      *
58501      * For example:
58502      *
58503      *      var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
58504      *      
58505      * @param {Object} sprite The Sprite to change the text.
58506      * @param {String} text The new text to be set.
58507      * @method
58508      */
58509     setText: Ext.emptyFn,
58510     
58511     //@private Creates an item and appends it to the surface. Called
58512     //as an internal method when calling `add`.
58513     createItem: Ext.emptyFn,
58514
58515     /**
58516      * Retrieves the id of this component.
58517      * Will autogenerate an id if one has not already been set.
58518      */
58519     getId: function() {
58520         return this.id || (this.id = Ext.id(null, 'ext-surface-'));
58521     },
58522
58523     /**
58524      * Destroys the surface. This is done by removing all components from it and
58525      * also removing its reference to a DOM element.
58526      *
58527      * For example:
58528      *
58529      *      drawComponent.surface.destroy();
58530      */
58531     destroy: function() {
58532         delete this.domRef;
58533         this.removeAll();
58534     }
58535 });
58536 /**
58537  * @class Ext.draw.Component
58538  * @extends Ext.Component
58539  *
58540  * The Draw Component is a surface in which sprites can be rendered. The Draw Component
58541  * manages and holds a `Surface` instance: an interface that has
58542  * an SVG or VML implementation depending on the browser capabilities and where
58543  * Sprites can be appended.
58544  * {@img Ext.draw.Component/Ext.draw.Component.png Ext.draw.Component component}
58545  * One way to create a draw component is:
58546  * 
58547  *     var drawComponent = Ext.create('Ext.draw.Component', {
58548  *         viewBox: false,
58549  *         items: [{
58550  *             type: 'circle',
58551  *             fill: '#79BB3F',
58552  *             radius: 100,
58553  *             x: 100,
58554  *             y: 100
58555  *         }]
58556  *     });
58557  *   
58558  *     Ext.create('Ext.Window', {
58559  *         width: 215,
58560  *         height: 235,
58561  *         layout: 'fit',
58562  *         items: [drawComponent]
58563  *     }).show();
58564  * 
58565  * In this case we created a draw component and added a sprite to it.
58566  * The *type* of the sprite is *circle* so if you run this code you'll see a yellow-ish
58567  * circle in a Window. When setting `viewBox` to `false` we are responsible for setting the object's position and
58568  * dimensions accordingly. 
58569  * 
58570  * You can also add sprites by using the surface's add method:
58571  *    
58572  *     drawComponent.surface.add({
58573  *         type: 'circle',
58574  *         fill: '#79BB3F',
58575  *         radius: 100,
58576  *         x: 100,
58577  *         y: 100
58578  *     });
58579  *  
58580  * For more information on Sprites, the core elements added to a draw component's surface,
58581  * refer to the Ext.draw.Sprite documentation.
58582  */
58583 Ext.define('Ext.draw.Component', {
58584
58585     /* Begin Definitions */
58586
58587     alias: 'widget.draw',
58588
58589     extend: 'Ext.Component',
58590
58591     requires: [
58592         'Ext.draw.Surface',
58593         'Ext.layout.component.Draw'
58594     ],
58595
58596     /* End Definitions */
58597
58598     /**
58599      * @cfg {Array} enginePriority
58600      * Defines the priority order for which Surface implementation to use. The first
58601      * one supported by the current environment will be used.
58602      */
58603     enginePriority: ['Svg', 'Vml'],
58604
58605     baseCls: Ext.baseCSSPrefix + 'surface',
58606
58607     componentLayout: 'draw',
58608
58609     /**
58610      * @cfg {Boolean} viewBox
58611      * Turn on view box support which will scale and position items in the draw component to fit to the component while
58612      * maintaining aspect ratio. Note that this scaling can override other sizing settings on yor items. Defaults to true.
58613      */
58614     viewBox: true,
58615
58616     /**
58617      * @cfg {Boolean} autoSize
58618      * Turn on autoSize support which will set the bounding div's size to the natural size of the contents. Defaults to false.
58619      */
58620     autoSize: false,
58621     
58622     /**
58623      * @cfg {Array} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
58624      * The gradients array is an array of objects with the following properties:
58625      *
58626      * <ul>
58627      * <li><strong>id</strong> - string - The unique name of the gradient.</li>
58628      * <li><strong>angle</strong> - number, optional - The angle of the gradient in degrees.</li>
58629      * <li><strong>stops</strong> - object - An object with numbers as keys (from 0 to 100) and style objects
58630      * as values</li>
58631      * </ul>
58632      * 
58633      
58634      For example:
58635      
58636      <pre><code>
58637         gradients: [{
58638             id: 'gradientId',
58639             angle: 45,
58640             stops: {
58641                 0: {
58642                     color: '#555'
58643                 },
58644                 100: {
58645                     color: '#ddd'
58646                 }
58647             }
58648         },  {
58649             id: 'gradientId2',
58650             angle: 0,
58651             stops: {
58652                 0: {
58653                     color: '#590'
58654                 },
58655                 20: {
58656                     color: '#599'
58657                 },
58658                 100: {
58659                     color: '#ddd'
58660                 }
58661             }
58662         }]
58663      </code></pre>
58664      
58665      Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
58666      
58667      <pre><code>
58668         sprite.setAttributes({
58669             fill: 'url(#gradientId)'
58670         }, true);
58671      </code></pre>
58672      
58673      */
58674
58675     initComponent: function() {
58676         this.callParent(arguments);
58677
58678         this.addEvents(
58679             'mousedown',
58680             'mouseup',
58681             'mousemove',
58682             'mouseenter',
58683             'mouseleave',
58684             'click'
58685         );
58686     },
58687
58688     /**
58689      * @private
58690      *
58691      * Create the Surface on initial render
58692      */
58693     onRender: function() {
58694         var me = this,
58695             viewBox = me.viewBox,
58696             autoSize = me.autoSize,
58697             bbox, items, width, height, x, y;
58698         me.callParent(arguments);
58699
58700         me.createSurface();
58701
58702         items = me.surface.items;
58703
58704         if (viewBox || autoSize) {
58705             bbox = items.getBBox();
58706             width = bbox.width;
58707             height = bbox.height;
58708             x = bbox.x;
58709             y = bbox.y;
58710             if (me.viewBox) {
58711                 me.surface.setViewBox(x, y, width, height);
58712             }
58713             else {
58714                 // AutoSized
58715                 me.autoSizeSurface();
58716             }
58717         }
58718     },
58719
58720     //@private
58721     autoSizeSurface: function() {
58722         var me = this,
58723             items = me.surface.items,
58724             bbox = items.getBBox(),
58725             width = bbox.width,
58726             height = bbox.height;
58727         items.setAttributes({
58728             translate: {
58729                 x: -bbox.x,
58730                 //Opera has a slight offset in the y axis.
58731                 y: -bbox.y + (+Ext.isOpera)
58732             }
58733         }, true);
58734         if (me.rendered) {
58735             me.setSize(width, height);
58736             me.surface.setSize(width, height);
58737         }
58738         else {
58739             me.surface.setSize(width, height);
58740         }
58741         me.el.setSize(width, height);
58742     },
58743
58744     /**
58745      * Create the Surface instance. Resolves the correct Surface implementation to
58746      * instantiate based on the 'enginePriority' config. Once the Surface instance is
58747      * created you can use the handle to that instance to add sprites. For example:
58748      *
58749      <pre><code>
58750         drawComponent.surface.add(sprite);
58751      </code></pre>
58752      */
58753     createSurface: function() {
58754         var surface = Ext.draw.Surface.create(Ext.apply({}, {
58755                 width: this.width,
58756                 height: this.height,
58757                 renderTo: this.el
58758             }, this.initialConfig));
58759         this.surface = surface;
58760
58761         function refire(eventName) {
58762             return function(e) {
58763                 this.fireEvent(eventName, e);
58764             };
58765         }
58766
58767         surface.on({
58768             scope: this,
58769             mouseup: refire('mouseup'),
58770             mousedown: refire('mousedown'),
58771             mousemove: refire('mousemove'),
58772             mouseenter: refire('mouseenter'),
58773             mouseleave: refire('mouseleave'),
58774             click: refire('click')
58775         });
58776     },
58777
58778
58779     /**
58780      * @private
58781      * 
58782      * Clean up the Surface instance on component destruction
58783      */
58784     onDestroy: function() {
58785         var surface = this.surface;
58786         if (surface) {
58787             surface.destroy();
58788         }
58789         this.callParent(arguments);
58790     }
58791
58792 });
58793
58794 /**
58795  * @class Ext.chart.LegendItem
58796  * @extends Ext.draw.CompositeSprite
58797  * A single item of a legend (marker plus label)
58798  * @constructor
58799  */
58800 Ext.define('Ext.chart.LegendItem', {
58801
58802     /* Begin Definitions */
58803
58804     extend: 'Ext.draw.CompositeSprite',
58805
58806     requires: ['Ext.chart.Shape'],
58807
58808     /* End Definitions */
58809
58810     // Position of the item, relative to the upper-left corner of the legend box
58811     x: 0,
58812     y: 0,
58813     zIndex: 500,
58814
58815     constructor: function(config) {
58816         this.callParent(arguments);
58817         this.createLegend(config);
58818     },
58819
58820     /**
58821      * Creates all the individual sprites for this legend item
58822      */
58823     createLegend: function(config) {
58824         var me = this,
58825             index = config.yFieldIndex,
58826             series = me.series,
58827             seriesType = series.type,
58828             idx = me.yFieldIndex,
58829             legend = me.legend,
58830             surface = me.surface,
58831             refX = legend.x + me.x,
58832             refY = legend.y + me.y,
58833             bbox, z = me.zIndex,
58834             markerConfig, label, mask,
58835             radius, toggle = false,
58836             seriesStyle = Ext.apply(series.seriesStyle, series.style);
58837
58838         function getSeriesProp(name) {
58839             var val = series[name];
58840             return (Ext.isArray(val) ? val[idx] : val);
58841         }
58842         
58843         label = me.add('label', surface.add({
58844             type: 'text',
58845             x: 20,
58846             y: 0,
58847             zIndex: z || 0,
58848             font: legend.labelFont,
58849             text: getSeriesProp('title') || getSeriesProp('yField')
58850         }));
58851
58852         // Line series - display as short line with optional marker in the middle
58853         if (seriesType === 'line' || seriesType === 'scatter') {
58854             if(seriesType === 'line') {
58855                 me.add('line', surface.add({
58856                     type: 'path',
58857                     path: 'M0.5,0.5L16.5,0.5',
58858                     zIndex: z,
58859                     "stroke-width": series.lineWidth,
58860                     "stroke-linejoin": "round",
58861                     "stroke-dasharray": series.dash,
58862                     stroke: seriesStyle.stroke || '#000',
58863                     style: {
58864                         cursor: 'pointer'
58865                     }
58866                 }));
58867             }
58868             if (series.showMarkers || seriesType === 'scatter') {
58869                 markerConfig = Ext.apply(series.markerStyle, series.markerConfig || {});
58870                 me.add('marker', Ext.chart.Shape[markerConfig.type](surface, {
58871                     fill: markerConfig.fill,
58872                     x: 8.5,
58873                     y: 0.5,
58874                     zIndex: z,
58875                     radius: markerConfig.radius || markerConfig.size,
58876                     style: {
58877                         cursor: 'pointer'
58878                     }
58879                 }));
58880             }
58881         }
58882         // All other series types - display as filled box
58883         else {
58884             me.add('box', surface.add({
58885                 type: 'rect',
58886                 zIndex: z,
58887                 x: 0,
58888                 y: 0,
58889                 width: 12,
58890                 height: 12,
58891                 fill: series.getLegendColor(index),
58892                 style: {
58893                     cursor: 'pointer'
58894                 }
58895             }));
58896         }
58897         
58898         me.setAttributes({
58899             hidden: false
58900         }, true);
58901         
58902         bbox = me.getBBox();
58903         
58904         mask = me.add('mask', surface.add({
58905             type: 'rect',
58906             x: bbox.x,
58907             y: bbox.y,
58908             width: bbox.width || 20,
58909             height: bbox.height || 20,
58910             zIndex: (z || 0) + 1000,
58911             fill: '#f00',
58912             opacity: 0,
58913             style: {
58914                 'cursor': 'pointer'
58915             }
58916         }));
58917
58918         //add toggle listener
58919         me.on('mouseover', function() {
58920             label.setStyle({
58921                 'font-weight': 'bold'
58922             });
58923             mask.setStyle({
58924                 'cursor': 'pointer'
58925             });
58926             series._index = index;
58927             series.highlightItem();
58928         }, me);
58929
58930         me.on('mouseout', function() {
58931             label.setStyle({
58932                 'font-weight': 'normal'
58933             });
58934             series._index = index;
58935             series.unHighlightItem();
58936         }, me);
58937         
58938         if (!series.visibleInLegend(index)) {
58939             toggle = true;
58940             label.setAttributes({
58941                opacity: 0.5
58942             }, true);
58943         }
58944
58945         me.on('mousedown', function() {
58946             if (!toggle) {
58947                 series.hideAll();
58948                 label.setAttributes({
58949                     opacity: 0.5
58950                 }, true);
58951             } else {
58952                 series.showAll();
58953                 label.setAttributes({
58954                     opacity: 1
58955                 }, true);
58956             }
58957             toggle = !toggle;
58958         }, me);
58959         me.updatePosition({x:0, y:0}); //Relative to 0,0 at first so that the bbox is calculated correctly
58960     },
58961
58962     /**
58963      * Update the positions of all this item's sprites to match the root position
58964      * of the legend box.
58965      * @param {Object} relativeTo (optional) If specified, this object's 'x' and 'y' values will be used
58966      *                 as the reference point for the relative positioning. Defaults to the Legend.
58967      */
58968     updatePosition: function(relativeTo) {
58969         var me = this,
58970             items = me.items,
58971             ln = items.length,
58972             i = 0,
58973             item;
58974         if (!relativeTo) {
58975             relativeTo = me.legend;
58976         }
58977         for (; i < ln; i++) {
58978             item = items[i];
58979             switch (item.type) {
58980                 case 'text':
58981                     item.setAttributes({
58982                         x: 20 + relativeTo.x + me.x,
58983                         y: relativeTo.y + me.y
58984                     }, true);
58985                     break;
58986                 case 'rect':
58987                     item.setAttributes({
58988                         translate: {
58989                             x: relativeTo.x + me.x,
58990                             y: relativeTo.y + me.y - 6
58991                         }
58992                     }, true);
58993                     break;
58994                 default:
58995                     item.setAttributes({
58996                         translate: {
58997                             x: relativeTo.x + me.x,
58998                             y: relativeTo.y + me.y
58999                         }
59000                     }, true);
59001             }
59002         }
59003     }
59004 });
59005 /**
59006  * @class Ext.chart.Legend
59007  *
59008  * Defines a legend for a chart's series.
59009  * The 'chart' member must be set prior to rendering.
59010  * The legend class displays a list of legend items each of them related with a
59011  * series being rendered. In order to render the legend item of the proper series
59012  * the series configuration object must have `showInSeries` set to true.
59013  *
59014  * The legend configuration object accepts a `position` as parameter.
59015  * The `position` parameter can be `left`, `right`
59016  * `top` or `bottom`. For example:
59017  *
59018  *     legend: {
59019  *         position: 'right'
59020  *     },
59021  * 
59022  * Full example:
59023     <pre><code>
59024     var store = Ext.create('Ext.data.JsonStore', {
59025         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
59026         data: [
59027             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
59028             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
59029             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
59030             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
59031             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
59032         ]
59033     });
59034     
59035     Ext.create('Ext.chart.Chart', {
59036         renderTo: Ext.getBody(),
59037         width: 500,
59038         height: 300,
59039         animate: true,
59040         store: store,
59041         shadow: true,
59042         theme: 'Category1',
59043         legend: {
59044             position: 'top'
59045         },
59046          axes: [{
59047                 type: 'Numeric',
59048                 grid: true,
59049                 position: 'left',
59050                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
59051                 title: 'Sample Values',
59052                 grid: {
59053                     odd: {
59054                         opacity: 1,
59055                         fill: '#ddd',
59056                         stroke: '#bbb',
59057                         'stroke-width': 1
59058                     }
59059                 },
59060                 minimum: 0,
59061                 adjustMinimumByMajorUnit: 0
59062             }, {
59063                 type: 'Category',
59064                 position: 'bottom',
59065                 fields: ['name'],
59066                 title: 'Sample Metrics',
59067                 grid: true,
59068                 label: {
59069                     rotate: {
59070                         degrees: 315
59071                     }
59072                 }
59073         }],
59074         series: [{
59075             type: 'area',
59076             highlight: false,
59077             axis: 'left',
59078             xField: 'name',
59079             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
59080             style: {
59081                 opacity: 0.93
59082             }
59083         }]
59084     });    
59085     </code></pre>    
59086  *
59087  * @constructor
59088  */
59089 Ext.define('Ext.chart.Legend', {
59090
59091     /* Begin Definitions */
59092
59093     requires: ['Ext.chart.LegendItem'],
59094
59095     /* End Definitions */
59096
59097     /**
59098      * @cfg {Boolean} visible
59099      * Whether or not the legend should be displayed.
59100      */
59101     visible: true,
59102
59103     /**
59104      * @cfg {String} position
59105      * The position of the legend in relation to the chart. One of: "top",
59106      * "bottom", "left", "right", or "float". If set to "float", then the legend
59107      * box will be positioned at the point denoted by the x and y parameters.
59108      */
59109     position: 'bottom',
59110
59111     /**
59112      * @cfg {Number} x
59113      * X-position of the legend box. Used directly if position is set to "float", otherwise 
59114      * it will be calculated dynamically.
59115      */
59116     x: 0,
59117
59118     /**
59119      * @cfg {Number} y
59120      * Y-position of the legend box. Used directly if position is set to "float", otherwise
59121      * it will be calculated dynamically.
59122      */
59123     y: 0,
59124
59125     /**
59126      * @cfg {String} labelFont
59127      * Font to be used for the legend labels, eg '12px Helvetica'
59128      */
59129     labelFont: '12px Helvetica, sans-serif',
59130
59131     /**
59132      * @cfg {String} boxStroke
59133      * Style of the stroke for the legend box
59134      */
59135     boxStroke: '#000',
59136
59137     /**
59138      * @cfg {String} boxStrokeWidth
59139      * Width of the stroke for the legend box
59140      */
59141     boxStrokeWidth: 1,
59142
59143     /**
59144      * @cfg {String} boxFill
59145      * Fill style for the legend box
59146      */
59147     boxFill: '#FFF',
59148
59149     /**
59150      * @cfg {Number} itemSpacing
59151      * Amount of space between legend items
59152      */
59153     itemSpacing: 10,
59154
59155     /**
59156      * @cfg {Number} padding
59157      * Amount of padding between the legend box's border and its items
59158      */
59159     padding: 5,
59160
59161     // @private
59162     width: 0,
59163     // @private
59164     height: 0,
59165
59166     /**
59167      * @cfg {Number} boxZIndex
59168      * Sets the z-index for the legend. Defaults to 100.
59169      */
59170     boxZIndex: 100,
59171
59172     constructor: function(config) {
59173         var me = this;
59174         if (config) {
59175             Ext.apply(me, config);
59176         }
59177         me.items = [];
59178         /**
59179          * Whether the legend box is oriented vertically, i.e. if it is on the left or right side or floating.
59180          * @type {Boolean}
59181          */
59182         me.isVertical = ("left|right|float".indexOf(me.position) !== -1);
59183         
59184         // cache these here since they may get modified later on
59185         me.origX = me.x;
59186         me.origY = me.y;
59187     },
59188
59189     /**
59190      * @private Create all the sprites for the legend
59191      */
59192     create: function() {
59193         var me = this;
59194         me.createItems();
59195         if (!me.created && me.isDisplayed()) {
59196             me.createBox();
59197             me.created = true;
59198
59199             // Listen for changes to series titles to trigger regeneration of the legend
59200             me.chart.series.each(function(series) {
59201                 series.on('titlechange', function() {
59202                     me.create();
59203                     me.updatePosition();
59204                 });
59205             });
59206         }
59207     },
59208
59209     /**
59210      * @private Determine whether the legend should be displayed. Looks at the legend's 'visible' config,
59211      * and also the 'showInLegend' config for each of the series.
59212      */
59213     isDisplayed: function() {
59214         return this.visible && this.chart.series.findIndex('showInLegend', true) !== -1;
59215     },
59216
59217     /**
59218      * @private Create the series markers and labels
59219      */
59220     createItems: function() {
59221         var me = this,
59222             chart = me.chart,
59223             surface = chart.surface,
59224             items = me.items,
59225             padding = me.padding,
59226             itemSpacing = me.itemSpacing,
59227             spacingOffset = 2,
59228             maxWidth = 0,
59229             maxHeight = 0,
59230             totalWidth = 0,
59231             totalHeight = 0,
59232             vertical = me.isVertical,
59233             math = Math,
59234             mfloor = math.floor,
59235             mmax = math.max,
59236             index = 0, 
59237             i = 0, 
59238             len = items ? items.length : 0,
59239             x, y, spacing, item, bbox, height, width;
59240
59241         //remove all legend items
59242         if (len) {
59243             for (; i < len; i++) {
59244                 items[i].destroy();
59245             }
59246         }
59247         //empty array
59248         items.length = [];
59249         // Create all the item labels, collecting their dimensions and positioning each one
59250         // properly in relation to the previous item
59251         chart.series.each(function(series, i) {
59252             if (series.showInLegend) {
59253                 Ext.each([].concat(series.yField), function(field, j) {
59254                     item = Ext.create('Ext.chart.LegendItem', {
59255                         legend: this,
59256                         series: series,
59257                         surface: chart.surface,
59258                         yFieldIndex: j
59259                     });
59260                     bbox = item.getBBox();
59261
59262                     //always measure from x=0, since not all markers go all the way to the left
59263                     width = bbox.width; 
59264                     height = bbox.height;
59265
59266                     if (i + j === 0) {
59267                         spacing = vertical ? padding + height / 2 : padding;
59268                     }
59269                     else {
59270                         spacing = itemSpacing / (vertical ? 2 : 1);
59271                     }
59272                     // Set the item's position relative to the legend box
59273                     item.x = mfloor(vertical ? padding : totalWidth + spacing);
59274                     item.y = mfloor(vertical ? totalHeight + spacing : padding + height / 2);
59275
59276                     // Collect cumulative dimensions
59277                     totalWidth += width + spacing;
59278                     totalHeight += height + spacing;
59279                     maxWidth = mmax(maxWidth, width);
59280                     maxHeight = mmax(maxHeight, height);
59281
59282                     items.push(item);
59283                 }, this);
59284             }
59285         }, me);
59286
59287         // Store the collected dimensions for later
59288         me.width = mfloor((vertical ? maxWidth : totalWidth) + padding * 2);
59289         if (vertical && items.length === 1) {
59290             spacingOffset = 1;
59291         }
59292         me.height = mfloor((vertical ? totalHeight - spacingOffset * spacing : maxHeight) + (padding * 2));
59293         me.itemHeight = maxHeight;
59294     },
59295
59296     /**
59297      * @private Get the bounds for the legend's outer box
59298      */
59299     getBBox: function() {
59300         var me = this;
59301         return {
59302             x: Math.round(me.x) - me.boxStrokeWidth / 2,
59303             y: Math.round(me.y) - me.boxStrokeWidth / 2,
59304             width: me.width,
59305             height: me.height
59306         };
59307     },
59308
59309     /**
59310      * @private Create the box around the legend items
59311      */
59312     createBox: function() {
59313         var me = this,
59314             box = me.boxSprite = me.chart.surface.add(Ext.apply({
59315                 type: 'rect',
59316                 stroke: me.boxStroke,
59317                 "stroke-width": me.boxStrokeWidth,
59318                 fill: me.boxFill,
59319                 zIndex: me.boxZIndex
59320             }, me.getBBox()));
59321         box.redraw();
59322     },
59323
59324     /**
59325      * @private Update the position of all the legend's sprites to match its current x/y values
59326      */
59327     updatePosition: function() {
59328         var me = this,
59329             x, y,
59330             legendWidth = me.width,
59331             legendHeight = me.height,
59332             padding = me.padding,
59333             chart = me.chart,
59334             chartBBox = chart.chartBBox,
59335             insets = chart.insetPadding,
59336             chartWidth = chartBBox.width - (insets * 2),
59337             chartHeight = chartBBox.height - (insets * 2),
59338             chartX = chartBBox.x + insets,
59339             chartY = chartBBox.y + insets,
59340             surface = chart.surface,
59341             mfloor = Math.floor;
59342         
59343         if (me.isDisplayed()) {
59344             // Find the position based on the dimensions
59345             switch(me.position) {
59346                 case "left":
59347                     x = insets;
59348                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
59349                     break;
59350                 case "right":
59351                     x = mfloor(surface.width - legendWidth) - insets;
59352                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
59353                     break;
59354                 case "top":
59355                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
59356                     y = insets;
59357                     break;
59358                 case "bottom":
59359                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
59360                     y = mfloor(surface.height - legendHeight) - insets;
59361                     break;
59362                 default:
59363                     x = mfloor(me.origX) + insets;
59364                     y = mfloor(me.origY) + insets;
59365             }
59366             me.x = x;
59367             me.y = y;
59368
59369             // Update the position of each item
59370             Ext.each(me.items, function(item) {
59371                 item.updatePosition();
59372             });
59373             // Update the position of the outer box
59374             me.boxSprite.setAttributes(me.getBBox(), true);
59375         }
59376     }
59377 });
59378 /**
59379  * @class Ext.chart.Chart
59380  * @extends Ext.draw.Component
59381  *
59382  * The Ext.chart package provides the capability to visualize data.
59383  * Each chart binds directly to an Ext.data.Store enabling automatic updates of the chart.
59384  * A chart configuration object has some overall styling options as well as an array of axes
59385  * and series. A chart instance example could look like:
59386  *
59387   <pre><code>
59388     Ext.create('Ext.chart.Chart', {
59389         renderTo: Ext.getBody(),
59390         width: 800,
59391         height: 600,
59392         animate: true,
59393         store: store1,
59394         shadow: true,
59395         theme: 'Category1',
59396         legend: {
59397             position: 'right'
59398         },
59399         axes: [ ...some axes options... ],
59400         series: [ ...some series options... ]
59401     });
59402   </code></pre>
59403  *
59404  * In this example we set the `width` and `height` of the chart, we decide whether our series are
59405  * animated or not and we select a store to be bound to the chart. We also turn on shadows for all series,
59406  * select a color theme `Category1` for coloring the series, set the legend to the right part of the chart and
59407  * then tell the chart to render itself in the body element of the document. For more information about the axes and
59408  * series configurations please check the documentation of each series (Line, Bar, Pie, etc).
59409  *
59410  * @xtype chart
59411  */
59412
59413 Ext.define('Ext.chart.Chart', {
59414
59415     /* Begin Definitions */
59416
59417     alias: 'widget.chart',
59418
59419     extend: 'Ext.draw.Component',
59420     
59421     mixins: {
59422         themeManager: 'Ext.chart.theme.Theme',
59423         mask: 'Ext.chart.Mask',
59424         navigation: 'Ext.chart.Navigation'
59425     },
59426
59427     requires: [
59428         'Ext.util.MixedCollection',
59429         'Ext.data.StoreManager',
59430         'Ext.chart.Legend',
59431         'Ext.util.DelayedTask'
59432     ],
59433
59434     /* End Definitions */
59435
59436     // @private
59437     viewBox: false,
59438
59439     /**
59440      * @cfg {String} theme (optional) The name of the theme to be used. A theme defines the colors and
59441      * other visual displays of tick marks on axis, text, title text, line colors, marker colors and styles, etc.
59442      * Possible theme values are 'Base', 'Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes
59443      * 'Category1' to 'Category6'. Default value is 'Base'.
59444      */
59445
59446     /**
59447      * @cfg {Boolean/Object} animate (optional) true for the default animation (easing: 'ease' and duration: 500)
59448      * or a standard animation config object to be used for default chart animations. Defaults to false.
59449      */
59450     animate: false,
59451
59452     /**
59453      * @cfg {Boolean/Object} legend (optional) true for the default legend display or a legend config object. Defaults to false.
59454      */
59455     legend: false,
59456
59457     /**
59458      * @cfg {integer} insetPadding (optional) Set the amount of inset padding in pixels for the chart. Defaults to 10.
59459      */
59460     insetPadding: 10,
59461
59462     /**
59463      * @cfg {Array} enginePriority
59464      * Defines the priority order for which Surface implementation to use. The first
59465      * one supported by the current environment will be used.
59466      */
59467     enginePriority: ['Svg', 'Vml'],
59468
59469     /**
59470      * @cfg {Object|Boolean} background (optional) Set the chart background. This can be a gradient object, image, or color.
59471      * Defaults to false for no background.
59472      *
59473      * For example, if `background` were to be a color we could set the object as
59474      *
59475      <pre><code>
59476         background: {
59477             //color string
59478             fill: '#ccc'
59479         }
59480      </code></pre>
59481
59482      You can specify an image by using:
59483
59484      <pre><code>
59485         background: {
59486             image: 'http://path.to.image/'
59487         }
59488      </code></pre>
59489
59490      Also you can specify a gradient by using the gradient object syntax:
59491
59492      <pre><code>
59493         background: {
59494             gradient: {
59495                 id: 'gradientId',
59496                 angle: 45,
59497                 stops: {
59498                     0: {
59499                         color: '#555'
59500                     }
59501                     100: {
59502                         color: '#ddd'
59503                     }
59504                 }
59505             }
59506         }
59507      </code></pre>
59508      */
59509     background: false,
59510
59511     /**
59512      * @cfg {Array} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
59513      * The gradients array is an array of objects with the following properties:
59514      *
59515      * <ul>
59516      * <li><strong>id</strong> - string - The unique name of the gradient.</li>
59517      * <li><strong>angle</strong> - number, optional - The angle of the gradient in degrees.</li>
59518      * <li><strong>stops</strong> - object - An object with numbers as keys (from 0 to 100) and style objects
59519      * as values</li>
59520      * </ul>
59521      *
59522
59523      For example:
59524
59525      <pre><code>
59526         gradients: [{
59527             id: 'gradientId',
59528             angle: 45,
59529             stops: {
59530                 0: {
59531                     color: '#555'
59532                 },
59533                 100: {
59534                     color: '#ddd'
59535                 }
59536             }
59537         },  {
59538             id: 'gradientId2',
59539             angle: 0,
59540             stops: {
59541                 0: {
59542                     color: '#590'
59543                 },
59544                 20: {
59545                     color: '#599'
59546                 },
59547                 100: {
59548                     color: '#ddd'
59549                 }
59550             }
59551         }]
59552      </code></pre>
59553
59554      Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
59555
59556      <pre><code>
59557         sprite.setAttributes({
59558             fill: 'url(#gradientId)'
59559         }, true);
59560      </code></pre>
59561
59562      */
59563
59564
59565     constructor: function(config) {
59566         var me = this,
59567             defaultAnim;
59568         me.initTheme(config.theme || me.theme);
59569         if (me.gradients) {
59570             Ext.apply(config, { gradients: me.gradients });
59571         }
59572         if (me.background) {
59573             Ext.apply(config, { background: me.background });
59574         }
59575         if (config.animate) {
59576             defaultAnim = {
59577                 easing: 'ease',
59578                 duration: 500
59579             };
59580             if (Ext.isObject(config.animate)) {
59581                 config.animate = Ext.applyIf(config.animate, defaultAnim);
59582             }
59583             else {
59584                 config.animate = defaultAnim;
59585             }
59586         }
59587         me.mixins.mask.constructor.call(me, config);
59588         me.mixins.navigation.constructor.call(me, config);
59589         me.callParent([config]);
59590     },
59591
59592     initComponent: function() {
59593         var me = this,
59594             axes,
59595             series;
59596         me.callParent();
59597         me.addEvents(
59598             'itemmousedown',
59599             'itemmouseup',
59600             'itemmouseover',
59601             'itemmouseout',
59602             'itemclick',
59603             'itemdoubleclick',
59604             'itemdragstart',
59605             'itemdrag',
59606             'itemdragend',
59607             /**
59608                  * @event beforerefresh
59609                  * Fires before a refresh to the chart data is called.  If the beforerefresh handler returns
59610                  * <tt>false</tt> the {@link #refresh} action will be cancelled.
59611                  * @param {Chart} this
59612                  */
59613             'beforerefresh',
59614             /**
59615                  * @event refresh
59616                  * Fires after the chart data has been refreshed.
59617                  * @param {Chart} this
59618                  */
59619             'refresh'
59620         );
59621         Ext.applyIf(me, {
59622             zoom: {
59623                 width: 1,
59624                 height: 1,
59625                 x: 0,
59626                 y: 0
59627             }
59628         });
59629         me.maxGutter = [0, 0];
59630         me.store = Ext.data.StoreManager.lookup(me.store);
59631         axes = me.axes;
59632         me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
59633         if (axes) {
59634             me.axes.addAll(axes);
59635         }
59636         series = me.series;
59637         me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
59638         if (series) {
59639             me.series.addAll(series);
59640         }
59641         if (me.legend !== false) {
59642             me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
59643         }
59644
59645         me.on({
59646             mousemove: me.onMouseMove,
59647             mouseleave: me.onMouseLeave,
59648             mousedown: me.onMouseDown,
59649             mouseup: me.onMouseUp,
59650             scope: me
59651         });
59652     },
59653
59654     // @private overrides the component method to set the correct dimensions to the chart.
59655     afterComponentLayout: function(width, height) {
59656         var me = this;
59657         if (Ext.isNumber(width) && Ext.isNumber(height)) {
59658             me.curWidth = width;
59659             me.curHeight = height;
59660             me.redraw(true);
59661         }
59662         this.callParent(arguments);
59663     },
59664
59665     /**
59666      * Redraw the chart. If animations are set this will animate the chart too.
59667      * @cfg {boolean} resize Optional flag which changes the default origin points of the chart for animations.
59668      */
59669     redraw: function(resize) {
59670         var me = this,
59671             chartBBox = me.chartBBox = {
59672                 x: 0,
59673                 y: 0,
59674                 height: me.curHeight,
59675                 width: me.curWidth
59676             },
59677             legend = me.legend;
59678         me.surface.setSize(chartBBox.width, chartBBox.height);
59679         // Instantiate Series and Axes
59680         me.series.each(me.initializeSeries, me);
59681         me.axes.each(me.initializeAxis, me);
59682         //process all views (aggregated data etc) on stores
59683         //before rendering.
59684         me.axes.each(function(axis) {
59685             axis.processView();
59686         });
59687         me.axes.each(function(axis) {
59688             axis.drawAxis(true);
59689         });
59690
59691         // Create legend if not already created
59692         if (legend !== false) {
59693             legend.create();
59694         }
59695
59696         // Place axes properly, including influence from each other
59697         me.alignAxes();
59698
59699         // Reposition legend based on new axis alignment
59700         if (me.legend !== false) {
59701             legend.updatePosition();
59702         }
59703
59704         // Find the max gutter
59705         me.getMaxGutter();
59706
59707         // Draw axes and series
59708         me.resizing = !!resize;
59709
59710         me.axes.each(me.drawAxis, me);
59711         me.series.each(me.drawCharts, me);
59712         me.resizing = false;
59713     },
59714
59715     // @private set the store after rendering the chart.
59716     afterRender: function() {
59717         var ref,
59718             me = this;
59719         this.callParent();
59720
59721         if (me.categoryNames) {
59722             me.setCategoryNames(me.categoryNames);
59723         }
59724
59725         if (me.tipRenderer) {
59726             ref = me.getFunctionRef(me.tipRenderer);
59727             me.setTipRenderer(ref.fn, ref.scope);
59728         }
59729         me.bindStore(me.store, true);
59730         me.refresh();
59731     },
59732
59733     // @private get x and y position of the mouse cursor.
59734     getEventXY: function(e) {
59735         var me = this,
59736             box = this.surface.getRegion(),
59737             pageXY = e.getXY(),
59738             x = pageXY[0] - box.left,
59739             y = pageXY[1] - box.top;
59740         return [x, y];
59741     },
59742
59743     // @private wrap the mouse down position to delegate the event to the series.
59744     onClick: function(e) {
59745         var me = this,
59746             position = me.getEventXY(e),
59747             item;
59748
59749         // Ask each series if it has an item corresponding to (not necessarily exactly
59750         // on top of) the current mouse coords. Fire itemclick event.
59751         me.series.each(function(series) {
59752             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
59753                 if (series.getItemForPoint) {
59754                     item = series.getItemForPoint(position[0], position[1]);
59755                     if (item) {
59756                         series.fireEvent('itemclick', item);
59757                     }
59758                 }
59759             }
59760         }, me);
59761     },
59762
59763     // @private wrap the mouse down position to delegate the event to the series.
59764     onMouseDown: function(e) {
59765         var me = this,
59766             position = me.getEventXY(e),
59767             item;
59768
59769         if (me.mask) {
59770             me.mixins.mask.onMouseDown.call(me, e);
59771         }
59772         // Ask each series if it has an item corresponding to (not necessarily exactly
59773         // on top of) the current mouse coords. Fire mousedown event.
59774         me.series.each(function(series) {
59775             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
59776                 if (series.getItemForPoint) {
59777                     item = series.getItemForPoint(position[0], position[1]);
59778                     if (item) {
59779                         series.fireEvent('itemmousedown', item);
59780                     }
59781                 }
59782             }
59783         }, me);
59784     },
59785
59786     // @private wrap the mouse up event to delegate it to the series.
59787     onMouseUp: function(e) {
59788         var me = this,
59789             position = me.getEventXY(e),
59790             item;
59791
59792         if (me.mask) {
59793             me.mixins.mask.onMouseUp.call(me, e);
59794         }
59795         // Ask each series if it has an item corresponding to (not necessarily exactly
59796         // on top of) the current mouse coords. Fire mousedown event.
59797         me.series.each(function(series) {
59798             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
59799                 if (series.getItemForPoint) {
59800                     item = series.getItemForPoint(position[0], position[1]);
59801                     if (item) {
59802                         series.fireEvent('itemmouseup', item);
59803                     }
59804                 }
59805             }
59806         }, me);
59807     },
59808
59809     // @private wrap the mouse move event so it can be delegated to the series.
59810     onMouseMove: function(e) {
59811         var me = this,
59812             position = me.getEventXY(e),
59813             item, last, storeItem, storeField;
59814
59815         if (me.mask) {
59816             me.mixins.mask.onMouseMove.call(me, e);
59817         }
59818         // Ask each series if it has an item corresponding to (not necessarily exactly
59819         // on top of) the current mouse coords. Fire itemmouseover/out events.
59820         me.series.each(function(series) {
59821             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
59822                 if (series.getItemForPoint) {
59823                     item = series.getItemForPoint(position[0], position[1]);
59824                     last = series._lastItemForPoint;
59825                     storeItem = series._lastStoreItem;
59826                     storeField = series._lastStoreField;
59827
59828
59829                     if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
59830                         if (last) {
59831                             series.fireEvent('itemmouseout', last);
59832                             delete series._lastItemForPoint;
59833                             delete series._lastStoreField;
59834                             delete series._lastStoreItem;
59835                         }
59836                         if (item) {
59837                             series.fireEvent('itemmouseover', item);
59838                             series._lastItemForPoint = item;
59839                             series._lastStoreItem = item.storeItem;
59840                             series._lastStoreField = item.storeField;
59841                         }
59842                     }
59843                 }
59844             } else {
59845                 last = series._lastItemForPoint;
59846                 if (last) {
59847                     series.fireEvent('itemmouseout', last);
59848                     delete series._lastItemForPoint;
59849                     delete series._lastStoreField;
59850                     delete series._lastStoreItem;
59851                 }
59852             }
59853         }, me);
59854     },
59855
59856     // @private handle mouse leave event.
59857     onMouseLeave: function(e) {
59858         var me = this;
59859         if (me.mask) {
59860             me.mixins.mask.onMouseLeave.call(me, e);
59861         }
59862         me.series.each(function(series) {
59863             delete series._lastItemForPoint;
59864         });
59865     },
59866
59867     // @private buffered refresh for when we update the store
59868     delayRefresh: function() {
59869         var me = this;
59870         if (!me.refreshTask) {
59871             me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
59872         }
59873         me.refreshTask.delay(me.refreshBuffer);
59874     },
59875
59876     // @private
59877     refresh: function() {
59878         var me = this;
59879         if (me.rendered && me.curWidth != undefined && me.curHeight != undefined) {
59880             if (me.fireEvent('beforerefresh', me) !== false) {
59881                 me.redraw();
59882                 me.fireEvent('refresh', me);
59883             }
59884         }
59885     },
59886
59887     /**
59888      * Changes the data store bound to this chart and refreshes it.
59889      * @param {Store} store The store to bind to this chart
59890      */
59891     bindStore: function(store, initial) {
59892         var me = this;
59893         if (!initial && me.store) {
59894             if (store !== me.store && me.store.autoDestroy) {
59895                 me.store.destroy();
59896             }
59897             else {
59898                 me.store.un('datachanged', me.refresh, me);
59899                 me.store.un('add', me.delayRefresh, me);
59900                 me.store.un('remove', me.delayRefresh, me);
59901                 me.store.un('update', me.delayRefresh, me);
59902                 me.store.un('clear', me.refresh, me);
59903             }
59904         }
59905         if (store) {
59906             store = Ext.data.StoreManager.lookup(store);
59907             store.on({
59908                 scope: me,
59909                 datachanged: me.refresh,
59910                 add: me.delayRefresh,
59911                 remove: me.delayRefresh,
59912                 update: me.delayRefresh,
59913                 clear: me.refresh
59914             });
59915         }
59916         me.store = store;
59917         if (store && !initial) {
59918             me.refresh();
59919         }
59920     },
59921
59922     // @private Create Axis
59923     initializeAxis: function(axis) {
59924         var me = this,
59925             chartBBox = me.chartBBox,
59926             w = chartBBox.width,
59927             h = chartBBox.height,
59928             x = chartBBox.x,
59929             y = chartBBox.y,
59930             themeAttrs = me.themeAttrs,
59931             config = {
59932                 chart: me
59933             };
59934         if (themeAttrs) {
59935             config.axisStyle = Ext.apply({}, themeAttrs.axis);
59936             config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
59937             config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
59938             config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
59939             config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
59940             config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
59941             config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
59942             config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
59943             config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
59944         }
59945         switch (axis.position) {
59946             case 'top':
59947                 Ext.apply(config, {
59948                     length: w,
59949                     width: h,
59950                     x: x,
59951                     y: y
59952                 });
59953             break;
59954             case 'bottom':
59955                 Ext.apply(config, {
59956                     length: w,
59957                     width: h,
59958                     x: x,
59959                     y: h
59960                 });
59961             break;
59962             case 'left':
59963                 Ext.apply(config, {
59964                     length: h,
59965                     width: w,
59966                     x: x,
59967                     y: h
59968                 });
59969             break;
59970             case 'right':
59971                 Ext.apply(config, {
59972                     length: h,
59973                     width: w,
59974                     x: w,
59975                     y: h
59976                 });
59977             break;
59978         }
59979         if (!axis.chart) {
59980             Ext.apply(config, axis);
59981             axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
59982         }
59983         else {
59984             Ext.apply(axis, config);
59985         }
59986     },
59987
59988
59989     /**
59990      * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
59991      * for the space taken up on each side by the axes and legend.
59992      */
59993     alignAxes: function() {
59994         var me = this,
59995             axes = me.axes,
59996             legend = me.legend,
59997             edges = ['top', 'right', 'bottom', 'left'],
59998             chartBBox,
59999             insetPadding = me.insetPadding,
60000             insets = {
60001                 top: insetPadding,
60002                 right: insetPadding,
60003                 bottom: insetPadding,
60004                 left: insetPadding
60005             };
60006
60007         function getAxis(edge) {
60008             var i = axes.findIndex('position', edge);
60009             return (i < 0) ? null : axes.getAt(i);
60010         }
60011
60012         // Find the space needed by axes and legend as a positive inset from each edge
60013         Ext.each(edges, function(edge) {
60014             var isVertical = (edge === 'left' || edge === 'right'),
60015                 axis = getAxis(edge),
60016                 bbox;
60017
60018             // Add legend size if it's on this edge
60019             if (legend !== false) {
60020                 if (legend.position === edge) {
60021                     bbox = legend.getBBox();
60022                     insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
60023                 }
60024             }
60025
60026             // Add axis size if there's one on this edge only if it has been
60027             //drawn before.
60028             if (axis && axis.bbox) {
60029                 bbox = axis.bbox;
60030                 insets[edge] += (isVertical ? bbox.width : bbox.height);
60031             }
60032         });
60033         // Build the chart bbox based on the collected inset values
60034         chartBBox = {
60035             x: insets.left,
60036             y: insets.top,
60037             width: me.curWidth - insets.left - insets.right,
60038             height: me.curHeight - insets.top - insets.bottom
60039         };
60040         me.chartBBox = chartBBox;
60041
60042         // Go back through each axis and set its length and position based on the
60043         // corresponding edge of the chartBBox
60044         axes.each(function(axis) {
60045             var pos = axis.position,
60046                 isVertical = (pos === 'left' || pos === 'right');
60047
60048             axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
60049             axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
60050             axis.width = (isVertical ? chartBBox.width : chartBBox.height);
60051             axis.length = (isVertical ? chartBBox.height : chartBBox.width);
60052         });
60053     },
60054
60055     // @private initialize the series.
60056     initializeSeries: function(series, idx) {
60057         var me = this,
60058             themeAttrs = me.themeAttrs,
60059             seriesObj, markerObj, seriesThemes, st,
60060             markerThemes, colorArrayStyle = [],
60061             i = 0, l,
60062             config = {
60063                 chart: me,
60064                 seriesId: series.seriesId
60065             };
60066         if (themeAttrs) {
60067             seriesThemes = themeAttrs.seriesThemes;
60068             markerThemes = themeAttrs.markerThemes;
60069             seriesObj = Ext.apply({}, themeAttrs.series);
60070             markerObj = Ext.apply({}, themeAttrs.marker);
60071             config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
60072             config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
60073             config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
60074             if (themeAttrs.colors) {
60075                 config.colorArrayStyle = themeAttrs.colors;
60076             } else {
60077                 colorArrayStyle = [];
60078                 for (l = seriesThemes.length; i < l; i++) {
60079                     st = seriesThemes[i];
60080                     if (st.fill || st.stroke) {
60081                         colorArrayStyle.push(st.fill || st.stroke);
60082                     }
60083                 }
60084                 if (colorArrayStyle.length) {
60085                     config.colorArrayStyle = colorArrayStyle;
60086                 }
60087             }
60088             config.seriesIdx = idx;
60089         }
60090         if (series instanceof Ext.chart.series.Series) {
60091             Ext.apply(series, config);
60092         } else {
60093             Ext.applyIf(config, series);
60094             series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
60095         }
60096         if (series.initialize) {
60097             series.initialize();
60098         }
60099     },
60100
60101     // @private
60102     getMaxGutter: function() {
60103         var me = this,
60104             maxGutter = [0, 0];
60105         me.series.each(function(s) {
60106             var gutter = s.getGutters && s.getGutters() || [0, 0];
60107             maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
60108             maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
60109         });
60110         me.maxGutter = maxGutter;
60111     },
60112
60113     // @private draw axis.
60114     drawAxis: function(axis) {
60115         axis.drawAxis();
60116     },
60117
60118     // @private draw series.
60119     drawCharts: function(series) {
60120         series.triggerafterrender = false;
60121         series.drawSeries();
60122         if (!this.animate) {
60123             series.fireEvent('afterrender');
60124         }
60125     },
60126
60127     // @private remove gently.
60128     destroy: function() {
60129         this.surface.destroy();
60130         this.bindStore(null);
60131         this.callParent(arguments);
60132     }
60133 });
60134
60135 /**
60136  * @class Ext.chart.Highlight
60137  * @ignore
60138  */
60139 Ext.define('Ext.chart.Highlight', {
60140
60141     /* Begin Definitions */
60142
60143     requires: ['Ext.fx.Anim'],
60144
60145     /* End Definitions */
60146
60147     /**
60148      * Highlight the given series item.
60149      * @param {Boolean|Object} Default's false. Can also be an object width style properties (i.e fill, stroke, radius) 
60150      * or just use default styles per series by setting highlight = true.
60151      */
60152     highlight: false,
60153
60154     highlightCfg : null,
60155
60156     constructor: function(config) {
60157         if (config.highlight) {
60158             if (config.highlight !== true) { //is an object
60159                 this.highlightCfg = Ext.apply({}, config.highlight);
60160             }
60161             else {
60162                 this.highlightCfg = {
60163                     fill: '#fdd',
60164                     radius: 20,
60165                     lineWidth: 5,
60166                     stroke: '#f55'
60167                 };
60168             }
60169         }
60170     },
60171
60172     /**
60173      * Highlight the given series item.
60174      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
60175      */
60176     highlightItem: function(item) {
60177         if (!item) {
60178             return;
60179         }
60180         
60181         var me = this,
60182             sprite = item.sprite,
60183             opts = me.highlightCfg,
60184             surface = me.chart.surface,
60185             animate = me.chart.animate,
60186             p,
60187             from,
60188             to,
60189             pi;
60190
60191         if (!me.highlight || !sprite || sprite._highlighted) {
60192             return;
60193         }
60194         if (sprite._anim) {
60195             sprite._anim.paused = true;
60196         }
60197         sprite._highlighted = true;
60198         if (!sprite._defaults) {
60199             sprite._defaults = Ext.apply(sprite._defaults || {},
60200             sprite.attr);
60201             from = {};
60202             to = {};
60203             for (p in opts) {
60204                 if (! (p in sprite._defaults)) {
60205                     sprite._defaults[p] = surface.availableAttrs[p];
60206                 }
60207                 from[p] = sprite._defaults[p];
60208                 to[p] = opts[p];
60209                 if (Ext.isObject(opts[p])) {
60210                     from[p] = {};
60211                     to[p] = {};
60212                     Ext.apply(sprite._defaults[p], sprite.attr[p]);
60213                     Ext.apply(from[p], sprite._defaults[p]);
60214                     for (pi in sprite._defaults[p]) {
60215                         if (! (pi in opts[p])) {
60216                             to[p][pi] = from[p][pi];
60217                         } else {
60218                             to[p][pi] = opts[p][pi];
60219                         }
60220                     }
60221                     for (pi in opts[p]) {
60222                         if (! (pi in to[p])) {
60223                             to[p][pi] = opts[p][pi];
60224                         }
60225                     }
60226                 }
60227             }
60228             sprite._from = from;
60229             sprite._to = to;
60230         }
60231         if (animate) {
60232             sprite._anim = Ext.create('Ext.fx.Anim', {
60233                 target: sprite,
60234                 from: sprite._from,
60235                 to: sprite._to,
60236                 duration: 150
60237             });
60238         } else {
60239             sprite.setAttributes(sprite._to, true);
60240         }
60241     },
60242
60243     /**
60244      * Un-highlight any existing highlights
60245      */
60246     unHighlightItem: function() {
60247         if (!this.highlight || !this.items) {
60248             return;
60249         }
60250
60251         var me = this,
60252             items = me.items,
60253             len = items.length,
60254             opts = me.highlightCfg,
60255             animate = me.chart.animate,
60256             i = 0,
60257             obj,
60258             p,
60259             sprite;
60260
60261         for (; i < len; i++) {
60262             if (!items[i]) {
60263                 continue;
60264             }
60265             sprite = items[i].sprite;
60266             if (sprite && sprite._highlighted) {
60267                 if (sprite._anim) {
60268                     sprite._anim.paused = true;
60269                 }
60270                 obj = {};
60271                 for (p in opts) {
60272                     if (Ext.isObject(sprite._defaults[p])) {
60273                         obj[p] = {};
60274                         Ext.apply(obj[p], sprite._defaults[p]);
60275                     }
60276                     else {
60277                         obj[p] = sprite._defaults[p];
60278                     }
60279                 }
60280                 if (animate) {
60281                     sprite._anim = Ext.create('Ext.fx.Anim', {
60282                         target: sprite,
60283                         to: obj,
60284                         duration: 150
60285                     });
60286                 }
60287                 else {
60288                     sprite.setAttributes(obj, true);
60289                 }
60290                 delete sprite._highlighted;
60291                 //delete sprite._defaults;
60292             }
60293         }
60294     },
60295
60296     cleanHighlights: function() {
60297         if (!this.highlight) {
60298             return;
60299         }
60300
60301         var group = this.group,
60302             markerGroup = this.markerGroup,
60303             i = 0,
60304             l;
60305         for (l = group.getCount(); i < l; i++) {
60306             delete group.getAt(i)._defaults;
60307         }
60308         if (markerGroup) {
60309             for (l = markerGroup.getCount(); i < l; i++) {
60310                 delete markerGroup.getAt(i)._defaults;
60311             }
60312         }
60313     }
60314 });
60315 /**
60316  * @class Ext.chart.Label
60317  *
60318  * Labels is a mixin whose methods are appended onto the Series class. Labels is an interface with methods implemented
60319  * in each of the Series (Pie, Bar, etc) for label creation and label placement.
60320  *
60321  * The methods implemented by the Series are:
60322  *  
60323  * - **`onCreateLabel(storeItem, item, i, display)`** Called each time a new label is created.
60324  *   The arguments of the method are:
60325  *   - *`storeItem`* The element of the store that is related to the label sprite.
60326  *   - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
60327  *     used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
60328  *   - *`i`* The index of the element created (i.e the first created label, second created label, etc)
60329  *   - *`display`* The display type. May be <b>false</b> if the label is hidden
60330  *
60331  *  - **`onPlaceLabel(label, storeItem, item, i, display, animate)`** Called for updating the position of the label.
60332  *    The arguments of the method are:
60333  *    - *`label`* The sprite label.</li>
60334  *    - *`storeItem`* The element of the store that is related to the label sprite</li>
60335  *    - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
60336  *      used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
60337  *    - *`i`* The index of the element to be updated (i.e. whether it is the first, second, third from the labelGroup)
60338  *    - *`display`* The display type. May be <b>false</b> if the label is hidden.
60339  *    - *`animate`* A boolean value to set or unset animations for the labels.
60340  */
60341 Ext.define('Ext.chart.Label', {
60342
60343     /* Begin Definitions */
60344
60345     requires: ['Ext.draw.Color'],
60346     
60347     /* End Definitions */
60348
60349     /**
60350      * @cfg {String} display
60351      * Specifies the presence and position of labels for each pie slice. Either "rotate", "middle", "insideStart",
60352      * "insideEnd", "outside", "over", "under", or "none" to prevent label rendering.
60353      * Default value: 'none'.
60354      */
60355
60356     /**
60357      * @cfg {String} color
60358      * The color of the label text.
60359      * Default value: '#000' (black).
60360      */
60361
60362     /**
60363      * @cfg {String} field
60364      * The name of the field to be displayed in the label.
60365      * Default value: 'name'.
60366      */
60367
60368     /**
60369      * @cfg {Number} minMargin
60370      * Specifies the minimum distance from a label to the origin of the visualization.
60371      * This parameter is useful when using PieSeries width variable pie slice lengths.
60372      * Default value: 50.
60373      */
60374
60375     /**
60376      * @cfg {String} font
60377      * The font used for the labels.
60378      * Defautl value: "11px Helvetica, sans-serif".
60379      */
60380
60381     /**
60382      * @cfg {String} orientation
60383      * Either "horizontal" or "vertical".
60384      * Dafault value: "horizontal".
60385      */
60386
60387     /**
60388      * @cfg {Function} renderer
60389      * Optional function for formatting the label into a displayable value.
60390      * Default value: function(v) { return v; }
60391      * @param v
60392      */
60393
60394     //@private a regex to parse url type colors.
60395     colorStringRe: /url\s*\(\s*#([^\/)]+)\s*\)/,
60396     
60397     //@private the mixin constructor. Used internally by Series.
60398     constructor: function(config) {
60399         var me = this;
60400         me.label = Ext.applyIf(me.label || {},
60401         {
60402             display: "none",
60403             color: "#000",
60404             field: "name",
60405             minMargin: 50,
60406             font: "11px Helvetica, sans-serif",
60407             orientation: "horizontal",
60408             renderer: function(v) {
60409                 return v;
60410             }
60411         });
60412
60413         if (me.label.display !== 'none') {
60414             me.labelsGroup = me.chart.surface.getGroup(me.seriesId + '-labels');
60415         }
60416     },
60417
60418     //@private a method to render all labels in the labelGroup
60419     renderLabels: function() {
60420         var me = this,
60421             chart = me.chart,
60422             gradients = chart.gradients,
60423             gradient,
60424             items = me.items,
60425             animate = chart.animate,
60426             config = me.label,
60427             display = config.display,
60428             color = config.color,
60429             field = [].concat(config.field),
60430             group = me.labelsGroup,
60431             store = me.chart.store,
60432             len = store.getCount(),
60433             ratio = items.length / len,
60434             i, count, index, j, 
60435             k, gradientsCount = (gradients || 0) && gradients.length,
60436             colorStopTotal, colorStopIndex, colorStop,
60437             item, label, storeItem,
60438             sprite, spriteColor, spriteBrightness, labelColor,
60439             Color = Ext.draw.Color,
60440             colorString;
60441
60442         if (display == 'none') {
60443             return;
60444         }
60445
60446         for (i = 0, count = 0; i < len; i++) {
60447             index = 0;
60448             for (j = 0; j < ratio; j++) {
60449                 item = items[count];
60450                 label = group.getAt(count);
60451                 storeItem = store.getAt(i);
60452                 
60453                 //check the excludes
60454                 while(this.__excludes && this.__excludes[index]) {
60455                     index++;
60456                 }
60457
60458                 if (!item && label) {
60459                     label.hide(true);
60460                 }
60461
60462                 if (item && field[j]) {
60463                     if (!label) {
60464                         label = me.onCreateLabel(storeItem, item, i, display, j, index);
60465                     }
60466                     me.onPlaceLabel(label, storeItem, item, i, display, animate, j, index);
60467
60468                     //set contrast
60469                     if (config.contrast && item.sprite) {
60470                         sprite = item.sprite;
60471                         colorString = sprite._to && sprite._to.fill || sprite.attr.fill;
60472                         spriteColor = Color.fromString(colorString);
60473                         //color wasn't parsed property maybe because it's a gradient id
60474                         if (colorString && !spriteColor) {
60475                             colorString = colorString.match(me.colorStringRe)[1];
60476                             for (k = 0; k < gradientsCount; k++) {
60477                                 gradient = gradients[k];
60478                                 if (gradient.id == colorString) {
60479                                     //avg color stops
60480                                     colorStop = 0; colorStopTotal = 0;
60481                                     for (colorStopIndex in gradient.stops) {
60482                                         colorStop++;
60483                                         colorStopTotal += Color.fromString(gradient.stops[colorStopIndex].color).getGrayscale();
60484                                     }
60485                                     spriteBrightness = (colorStopTotal / colorStop) / 255;
60486                                     break;
60487                                 }
60488                             }
60489                         }
60490                         else {
60491                             spriteBrightness = spriteColor.getGrayscale() / 255;
60492                         }
60493                         labelColor = Color.fromString(label.attr.color || label.attr.fill).getHSL();
60494                         
60495                         labelColor[2] = spriteBrightness > 0.5? 0.2 : 0.8;
60496                         label.setAttributes({
60497                             fill: String(Color.fromHSL.apply({}, labelColor))
60498                         }, true);
60499                     }
60500                 }
60501                 count++;
60502                 index++;
60503             }
60504         }
60505         me.hideLabels(count);
60506     },
60507
60508     //@private a method to hide labels.
60509     hideLabels: function(index) {
60510         var labelsGroup = this.labelsGroup, len;
60511         if (labelsGroup) {
60512             len = labelsGroup.getCount();
60513             while (len-->index) {
60514                 labelsGroup.getAt(len).hide(true);
60515             }
60516         }
60517     }
60518 });
60519 Ext.define('Ext.chart.MaskLayer', {
60520     extend: 'Ext.Component',
60521     
60522     constructor: function(config) {
60523         config = Ext.apply(config || {}, {
60524             style: 'position:absolute;background-color:#888;cursor:move;opacity:0.6;border:1px solid #222;'
60525         });
60526         this.callParent([config]);    
60527     },
60528     
60529     initComponent: function() {
60530         var me = this;
60531         me.callParent(arguments);
60532         me.addEvents(
60533             'mousedown',
60534             'mouseup',
60535             'mousemove',
60536             'mouseenter',
60537             'mouseleave'
60538         );
60539     },
60540
60541     initDraggable: function() {
60542         this.callParent(arguments);
60543         this.dd.onStart = function (e) {
60544             var me = this,
60545                 comp = me.comp;
60546     
60547             // Cache the start [X, Y] array
60548             this.startPosition = comp.getPosition(true);
60549     
60550             // If client Component has a ghost method to show a lightweight version of itself
60551             // then use that as a drag proxy unless configured to liveDrag.
60552             if (comp.ghost && !comp.liveDrag) {
60553                  me.proxy = comp.ghost();
60554                  me.dragTarget = me.proxy.header.el;
60555             }
60556     
60557             // Set the constrainTo Region before we start dragging.
60558             if (me.constrain || me.constrainDelegate) {
60559                 me.constrainTo = me.calculateConstrainRegion();
60560             }
60561         };
60562     }
60563 });
60564 /**
60565  * @class Ext.chart.TipSurface
60566  * @ignore
60567  */
60568 Ext.define('Ext.chart.TipSurface', {
60569
60570     /* Begin Definitions */
60571
60572     extend: 'Ext.draw.Component',
60573
60574     /* End Definitions */
60575
60576     spriteArray: false,
60577     renderFirst: true,
60578
60579     constructor: function(config) {
60580         this.callParent([config]);
60581         if (config.sprites) {
60582             this.spriteArray = [].concat(config.sprites);
60583             delete config.sprites;
60584         }
60585     },
60586
60587     onRender: function() {
60588         var me = this,
60589             i = 0,
60590             l = 0,
60591             sp,
60592             sprites;
60593             this.callParent(arguments);
60594         sprites = me.spriteArray;
60595         if (me.renderFirst && sprites) {
60596             me.renderFirst = false;
60597             for (l = sprites.length; i < l; i++) {
60598                 sp = me.surface.add(sprites[i]);
60599                 sp.setAttributes({
60600                     hidden: false
60601                 },
60602                 true);
60603             }
60604         }
60605     }
60606 });
60607
60608 /**
60609  * @class Ext.chart.Tip
60610  * @ignore
60611  */
60612 Ext.define('Ext.chart.Tip', {
60613
60614     /* Begin Definitions */
60615
60616     requires: ['Ext.tip.ToolTip', 'Ext.chart.TipSurface'],
60617
60618     /* End Definitions */
60619
60620     constructor: function(config) {
60621         var me = this,
60622             surface,
60623             sprites,
60624             tipSurface;
60625         if (config.tips) {
60626             me.tipTimeout = null;
60627             me.tipConfig = Ext.apply({}, config.tips, {
60628                 renderer: Ext.emptyFn,
60629                 constrainPosition: false
60630             });
60631             me.tooltip = Ext.create('Ext.tip.ToolTip', me.tipConfig);
60632             Ext.getBody().on('mousemove', me.tooltip.onMouseMove, me.tooltip);
60633             if (me.tipConfig.surface) {
60634                 //initialize a surface
60635                 surface = me.tipConfig.surface;
60636                 sprites = surface.sprites;
60637                 tipSurface = Ext.create('Ext.chart.TipSurface', {
60638                     id: 'tipSurfaceComponent',
60639                     sprites: sprites
60640                 });
60641                 if (surface.width && surface.height) {
60642                     tipSurface.setSize(surface.width, surface.height);
60643                 }
60644                 me.tooltip.add(tipSurface);
60645                 me.spriteTip = tipSurface;
60646             }
60647         }
60648     },
60649
60650     showTip: function(item) {
60651         var me = this;
60652         if (!me.tooltip) {
60653             return;
60654         }
60655         clearTimeout(me.tipTimeout);
60656         var tooltip = me.tooltip,
60657             spriteTip = me.spriteTip,
60658             tipConfig = me.tipConfig,
60659             trackMouse = tooltip.trackMouse,
60660             sprite, surface, surfaceExt, pos, x, y;
60661         if (!trackMouse) {
60662             tooltip.trackMouse = true;
60663             sprite = item.sprite;
60664             surface = sprite.surface;
60665             surfaceExt = Ext.get(surface.getId());
60666             if (surfaceExt) {
60667                 pos = surfaceExt.getXY();
60668                 x = pos[0] + (sprite.attr.x || 0) + (sprite.attr.translation && sprite.attr.translation.x || 0);
60669                 y = pos[1] + (sprite.attr.y || 0) + (sprite.attr.translation && sprite.attr.translation.y || 0);
60670                 tooltip.targetXY = [x, y];
60671             }
60672         }
60673         if (spriteTip) {
60674             tipConfig.renderer.call(tooltip, item.storeItem, item, spriteTip.surface);
60675         } else {
60676             tipConfig.renderer.call(tooltip, item.storeItem, item);
60677         }
60678         tooltip.show();
60679         tooltip.trackMouse = trackMouse;
60680     },
60681
60682     hideTip: function(item) {
60683         var tooltip = this.tooltip;
60684         if (!tooltip) {
60685             return;
60686         }
60687         clearTimeout(this.tipTimeout);
60688         this.tipTimeout = setTimeout(function() {
60689             tooltip.hide();
60690         }, 0);
60691     }
60692 });
60693 /**
60694  * @class Ext.chart.axis.Abstract
60695  * @ignore
60696  */
60697 Ext.define('Ext.chart.axis.Abstract', {
60698
60699     /* Begin Definitions */
60700
60701     requires: ['Ext.chart.Chart'],
60702
60703     /* End Definitions */
60704
60705     constructor: function(config) {
60706         config = config || {};
60707
60708         var me = this,
60709             pos = config.position || 'left';
60710
60711         pos = pos.charAt(0).toUpperCase() + pos.substring(1);
60712         //axisLabel(Top|Bottom|Right|Left)Style
60713         config.label = Ext.apply(config['axisLabel' + pos + 'Style'] || {}, config.label || {});
60714         config.axisTitleStyle = Ext.apply(config['axisTitle' + pos + 'Style'] || {}, config.labelTitle || {});
60715         Ext.apply(me, config);
60716         me.fields = [].concat(me.fields);
60717         this.callParent();
60718         me.labels = [];
60719         me.getId();
60720         me.labelGroup = me.chart.surface.getGroup(me.axisId + "-labels");
60721     },
60722
60723     alignment: null,
60724     grid: false,
60725     steps: 10,
60726     x: 0,
60727     y: 0,
60728     minValue: 0,
60729     maxValue: 0,
60730
60731     getId: function() {
60732         return this.axisId || (this.axisId = Ext.id(null, 'ext-axis-'));
60733     },
60734
60735     /*
60736       Called to process a view i.e to make aggregation and filtering over
60737       a store creating a substore to be used to render the axis. Since many axes
60738       may do different things on the data and we want the final result of all these
60739       operations to be rendered we need to call processView on all axes before drawing
60740       them.
60741     */
60742     processView: Ext.emptyFn,
60743
60744     drawAxis: Ext.emptyFn,
60745     addDisplayAndLabels: Ext.emptyFn
60746 });
60747
60748 /**
60749  * @class Ext.chart.axis.Axis
60750  * @extends Ext.chart.axis.Abstract
60751  * 
60752  * Defines axis for charts. The axis position, type, style can be configured.
60753  * The axes are defined in an axes array of configuration objects where the type, 
60754  * field, grid and other configuration options can be set. To know more about how 
60755  * to create a Chart please check the Chart class documentation. Here's an example for the axes part:
60756  * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
60757  * 
60758  *     axes: [{
60759  *         type: 'Numeric',
60760  *         grid: true,
60761  *         position: 'left',
60762  *         fields: ['data1', 'data2', 'data3'],
60763  *         title: 'Number of Hits',
60764  *         grid: {
60765  *             odd: {
60766  *                 opacity: 1,
60767  *                 fill: '#ddd',
60768  *                 stroke: '#bbb',
60769  *                 'stroke-width': 1
60770  *             }
60771  *         },
60772  *         minimum: 0
60773  *     }, {
60774  *         type: 'Category',
60775  *         position: 'bottom',
60776  *         fields: ['name'],
60777  *         title: 'Month of the Year',
60778  *         grid: true,
60779  *         label: {
60780  *             rotate: {
60781  *                 degrees: 315
60782  *             }
60783  *         }
60784  *     }]
60785  * 
60786  * 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
60787  * 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. 
60788  * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the 
60789  * category axis the labels will be rotated so they can fit the space better.
60790  */
60791 Ext.define('Ext.chart.axis.Axis', {
60792
60793     /* Begin Definitions */
60794
60795     extend: 'Ext.chart.axis.Abstract',
60796
60797     alternateClassName: 'Ext.chart.Axis',
60798
60799     requires: ['Ext.draw.Draw'],
60800
60801     /* End Definitions */
60802
60803     /**
60804      * @cfg {Number} majorTickSteps 
60805      * If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
60806      */
60807
60808     /**
60809      * @cfg {Number} minorTickSteps 
60810      * The number of small ticks between two major ticks. Default is zero.
60811      */
60812     
60813     //@private force min/max values from store
60814     forceMinMax: false,
60815     
60816     /**
60817      * @cfg {Number} dashSize 
60818      * The size of the dash marker. Default's 3.
60819      */
60820     dashSize: 3,
60821     
60822     /**
60823      * @cfg {String} position
60824      * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
60825      */
60826     position: 'bottom',
60827     
60828     // @private
60829     skipFirst: false,
60830     
60831     /**
60832      * @cfg {Number} length
60833      * Offset axis position. Default's 0.
60834      */
60835     length: 0,
60836     
60837     /**
60838      * @cfg {Number} width
60839      * Offset axis width. Default's 0.
60840      */
60841     width: 0,
60842     
60843     majorTickSteps: false,
60844
60845     // @private
60846     applyData: Ext.emptyFn,
60847
60848     // @private creates a structure with start, end and step points.
60849     calcEnds: function() {
60850         var me = this,
60851             math = Math,
60852             mmax = math.max,
60853             mmin = math.min,
60854             store = me.chart.substore || me.chart.store,
60855             series = me.chart.series.items,
60856             fields = me.fields,
60857             ln = fields.length,
60858             min = isNaN(me.minimum) ? Infinity : me.minimum,
60859             max = isNaN(me.maximum) ? -Infinity : me.maximum,
60860             prevMin = me.prevMin,
60861             prevMax = me.prevMax,
60862             aggregate = false,
60863             total = 0,
60864             excludes = [],
60865             outfrom, outto,
60866             i, l, values, rec, out;
60867
60868         //if one series is stacked I have to aggregate the values
60869         //for the scale.
60870         for (i = 0, l = series.length; !aggregate && i < l; i++) {
60871             aggregate = aggregate || series[i].stacked;
60872             excludes = series[i].__excludes || excludes;
60873         }
60874         store.each(function(record) {
60875             if (aggregate) {
60876                 if (!isFinite(min)) {
60877                     min = 0;
60878                 }
60879                 for (values = [0, 0], i = 0; i < ln; i++) {
60880                     if (excludes[i]) {
60881                         continue;
60882                     }
60883                     rec = record.get(fields[i]);
60884                     values[+(rec > 0)] += math.abs(rec);
60885                 }
60886                 max = mmax(max, -values[0], values[1]);
60887                 min = mmin(min, -values[0], values[1]);
60888             }
60889             else {
60890                 for (i = 0; i < ln; i++) {
60891                     if (excludes[i]) {
60892                         continue;
60893                     }
60894                     value = record.get(fields[i]);
60895                     max = mmax(max, value);
60896                     min = mmin(min, value);
60897                 }
60898             }
60899         });
60900         if (!isFinite(max)) {
60901             max = me.prevMax || 0;
60902         }
60903         if (!isFinite(min)) {
60904             min = me.prevMin || 0;
60905         }
60906         //normalize min max for snapEnds.
60907         if (min != max && (max != (max >> 0))) {
60908             max = (max >> 0) + 1;
60909         }
60910         out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ?  (me.majorTickSteps +1) : me.steps);
60911         outfrom = out.from;
60912         outto = out.to;
60913         if (me.forceMinMax) {
60914             if (!isNaN(max)) {
60915                 out.to = max;
60916             }
60917             if (!isNaN(min)) {
60918                 out.from = min;
60919             }
60920         }
60921         if (!isNaN(me.maximum)) {
60922             //TODO(nico) users are responsible for their own minimum/maximum values set.
60923             //Clipping should be added to remove lines in the chart which are below the axis.
60924             out.to = me.maximum;
60925         }
60926         if (!isNaN(me.minimum)) {
60927             //TODO(nico) users are responsible for their own minimum/maximum values set.
60928             //Clipping should be added to remove lines in the chart which are below the axis.
60929             out.from = me.minimum;
60930         }
60931         
60932         //Adjust after adjusting minimum and maximum
60933         out.step = (out.to - out.from) / (outto - outfrom) * out.step;
60934         
60935         if (me.adjustMaximumByMajorUnit) {
60936             out.to += out.step;
60937         }
60938         if (me.adjustMinimumByMajorUnit) {
60939             out.from -= out.step;
60940         }
60941         me.prevMin = min == max? 0 : min;
60942         me.prevMax = max;
60943         return out;
60944     },
60945
60946     /**
60947      * Renders the axis into the screen and updates it's position.
60948      */
60949     drawAxis: function (init) {
60950         var me = this,
60951             i, j,
60952             x = me.x,
60953             y = me.y,
60954             gutterX = me.chart.maxGutter[0],
60955             gutterY = me.chart.maxGutter[1],
60956             dashSize = me.dashSize,
60957             subDashesX = me.minorTickSteps || 0,
60958             subDashesY = me.minorTickSteps || 0,
60959             length = me.length,
60960             position = me.position,
60961             inflections = [],
60962             calcLabels = false,
60963             stepCalcs = me.applyData(),
60964             step = stepCalcs.step,
60965             steps = stepCalcs.steps,
60966             from = stepCalcs.from,
60967             to = stepCalcs.to,
60968             trueLength,
60969             currentX,
60970             currentY,
60971             path,
60972             prev,
60973             dashesX,
60974             dashesY,
60975             delta;
60976         
60977         //If no steps are specified
60978         //then don't draw the axis. This generally happens
60979         //when an empty store.
60980         if (me.hidden || isNaN(step) || (from == to)) {
60981             return;
60982         }
60983
60984         me.from = stepCalcs.from;
60985         me.to = stepCalcs.to;
60986         if (position == 'left' || position == 'right') {
60987             currentX = Math.floor(x) + 0.5;
60988             path = ["M", currentX, y, "l", 0, -length];
60989             trueLength = length - (gutterY * 2);
60990         }
60991         else {
60992             currentY = Math.floor(y) + 0.5;
60993             path = ["M", x, currentY, "l", length, 0];
60994             trueLength = length - (gutterX * 2);
60995         }
60996         
60997         delta = trueLength / (steps || 1);
60998         dashesX = Math.max(subDashesX +1, 0);
60999         dashesY = Math.max(subDashesY +1, 0);
61000         if (me.type == 'Numeric') {
61001             calcLabels = true;
61002             me.labels = [stepCalcs.from];
61003         }
61004         if (position == 'right' || position == 'left') {
61005             currentY = y - gutterY;
61006             currentX = x - ((position == 'left') * dashSize * 2);
61007             while (currentY >= y - gutterY - trueLength) {
61008                 path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
61009                 if (currentY != y - gutterY) {
61010                     for (i = 1; i < dashesY; i++) {
61011                         path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
61012                     }
61013                 }
61014                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
61015                 currentY -= delta;
61016                 if (calcLabels) {
61017                     me.labels.push(me.labels[me.labels.length -1] + step);
61018                 }
61019                 if (delta === 0) {
61020                     break;
61021                 }
61022             }
61023             if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
61024                 path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
61025                 for (i = 1; i < dashesY; i++) {
61026                     path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
61027                 }
61028                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
61029                 if (calcLabels) {
61030                     me.labels.push(me.labels[me.labels.length -1] + step);
61031                 }
61032             }
61033         } else {
61034             currentX = x + gutterX;
61035             currentY = y - ((position == 'top') * dashSize * 2);
61036             while (currentX <= x + gutterX + trueLength) {
61037                 path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
61038                 if (currentX != x + gutterX) {
61039                     for (i = 1; i < dashesX; i++) {
61040                         path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
61041                     }
61042                 }
61043                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
61044                 currentX += delta;
61045                 if (calcLabels) {
61046                     me.labels.push(me.labels[me.labels.length -1] + step);
61047                 }
61048                 if (delta === 0) {
61049                     break;
61050                 }
61051             }
61052             if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
61053                 path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
61054                 for (i = 1; i < dashesX; i++) {
61055                     path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
61056                 }
61057                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
61058                 if (calcLabels) {
61059                     me.labels.push(me.labels[me.labels.length -1] + step);
61060                 }
61061             }
61062         }
61063         if (!me.axis) {
61064             me.axis = me.chart.surface.add(Ext.apply({
61065                 type: 'path',
61066                 path: path
61067             }, me.axisStyle));
61068         }
61069         me.axis.setAttributes({
61070             path: path
61071         }, true);
61072         me.inflections = inflections;
61073         if (!init && me.grid) {
61074             me.drawGrid();
61075         }
61076         me.axisBBox = me.axis.getBBox();
61077         me.drawLabel();
61078     },
61079
61080     /**
61081      * Renders an horizontal and/or vertical grid into the Surface.
61082      */
61083     drawGrid: function() {
61084         var me = this,
61085             surface = me.chart.surface, 
61086             grid = me.grid,
61087             odd = grid.odd,
61088             even = grid.even,
61089             inflections = me.inflections,
61090             ln = inflections.length - ((odd || even)? 0 : 1),
61091             position = me.position,
61092             gutter = me.chart.maxGutter,
61093             width = me.width - 2,
61094             vert = false,
61095             point, prevPoint,
61096             i = 1,
61097             path = [], styles, lineWidth, dlineWidth,
61098             oddPath = [], evenPath = [];
61099         
61100         if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
61101             (gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
61102             i = 0;
61103             ln++;
61104         }
61105         for (; i < ln; i++) {
61106             point = inflections[i];
61107             prevPoint = inflections[i - 1];
61108             if (odd || even) {
61109                 path = (i % 2)? oddPath : evenPath;
61110                 styles = ((i % 2)? odd : even) || {};
61111                 lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
61112                 dlineWidth = 2 * lineWidth;
61113                 if (position == 'left') {
61114                     path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth, 
61115                               "L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
61116                               "L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
61117                               "L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
61118                 }
61119                 else if (position == 'right') {
61120                     path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth, 
61121                               "L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
61122                               "L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
61123                               "L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
61124                 }
61125                 else if (position == 'top') {
61126                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth, 
61127                               "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
61128                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
61129                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
61130                 }
61131                 else {
61132                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth, 
61133                             "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
61134                             "L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
61135                             "L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
61136                 }
61137             } else {
61138                 if (position == 'left') {
61139                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]);
61140                 }
61141                 else if (position == 'right') {
61142                     path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]);
61143                 }
61144                 else if (position == 'top') {
61145                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]);
61146                 }
61147                 else {
61148                     path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]);
61149                 }
61150             }
61151         }
61152         if (odd || even) {
61153             if (oddPath.length) {
61154                 if (!me.gridOdd && oddPath.length) {
61155                     me.gridOdd = surface.add({
61156                         type: 'path',
61157                         path: oddPath
61158                     });
61159                 }
61160                 me.gridOdd.setAttributes(Ext.apply({
61161                     path: oddPath,
61162                     hidden: false
61163                 }, odd || {}), true);
61164             }
61165             if (evenPath.length) {
61166                 if (!me.gridEven) {
61167                     me.gridEven = surface.add({
61168                         type: 'path',
61169                         path: evenPath
61170                     });
61171                 } 
61172                 me.gridEven.setAttributes(Ext.apply({
61173                     path: evenPath,
61174                     hidden: false
61175                 }, even || {}), true);
61176             }
61177         }
61178         else {
61179             if (path.length) {
61180                 if (!me.gridLines) {
61181                     me.gridLines = me.chart.surface.add({
61182                         type: 'path',
61183                         path: path,
61184                         "stroke-width": me.lineWidth || 1,
61185                         stroke: me.gridColor || '#ccc'
61186                     });
61187                 }
61188                 me.gridLines.setAttributes({
61189                     hidden: false,
61190                     path: path
61191                 }, true);
61192             }
61193             else if (me.gridLines) {
61194                 me.gridLines.hide(true);
61195             }
61196         }
61197     },
61198
61199     //@private
61200     getOrCreateLabel: function(i, text) {
61201         var me = this,
61202             labelGroup = me.labelGroup,
61203             textLabel = labelGroup.getAt(i),
61204             surface = me.chart.surface;
61205         if (textLabel) {
61206             if (text != textLabel.attr.text) {
61207                 textLabel.setAttributes(Ext.apply({
61208                     text: text
61209                 }, me.label), true);
61210                 textLabel._bbox = textLabel.getBBox();
61211             }
61212         }
61213         else {
61214             textLabel = surface.add(Ext.apply({
61215                 group: labelGroup,
61216                 type: 'text',
61217                 x: 0,
61218                 y: 0,
61219                 text: text
61220             }, me.label));
61221             surface.renderItem(textLabel);
61222             textLabel._bbox = textLabel.getBBox();
61223         }
61224         //get untransformed bounding box
61225         if (me.label.rotation) {
61226             textLabel.setAttributes({
61227                 rotation: {
61228                     degrees: 0    
61229                 }    
61230             }, true);
61231             textLabel._ubbox = textLabel.getBBox();
61232             textLabel.setAttributes(me.label, true);
61233         } else {
61234             textLabel._ubbox = textLabel._bbox;
61235         }
61236         return textLabel;
61237     },
61238     
61239     rect2pointArray: function(sprite) {
61240         var surface = this.chart.surface,
61241             rect = surface.getBBox(sprite, true),
61242             p1 = [rect.x, rect.y],
61243             p1p = p1.slice(),
61244             p2 = [rect.x + rect.width, rect.y],
61245             p2p = p2.slice(),
61246             p3 = [rect.x + rect.width, rect.y + rect.height],
61247             p3p = p3.slice(),
61248             p4 = [rect.x, rect.y + rect.height],
61249             p4p = p4.slice(),
61250             matrix = sprite.matrix;
61251         //transform the points
61252         p1[0] = matrix.x.apply(matrix, p1p);
61253         p1[1] = matrix.y.apply(matrix, p1p);
61254         
61255         p2[0] = matrix.x.apply(matrix, p2p);
61256         p2[1] = matrix.y.apply(matrix, p2p);
61257         
61258         p3[0] = matrix.x.apply(matrix, p3p);
61259         p3[1] = matrix.y.apply(matrix, p3p);
61260         
61261         p4[0] = matrix.x.apply(matrix, p4p);
61262         p4[1] = matrix.y.apply(matrix, p4p);
61263         return [p1, p2, p3, p4];
61264     },
61265     
61266     intersect: function(l1, l2) {
61267         var r1 = this.rect2pointArray(l1),
61268             r2 = this.rect2pointArray(l2);
61269         return !!Ext.draw.Draw.intersect(r1, r2).length;
61270     },
61271     
61272     drawHorizontalLabels: function() {
61273        var  me = this,
61274             labelConf = me.label,
61275             floor = Math.floor,
61276             max = Math.max,
61277             axes = me.chart.axes,
61278             position = me.position,
61279             inflections = me.inflections,
61280             ln = inflections.length,
61281             labels = me.labels,
61282             labelGroup = me.labelGroup,
61283             maxHeight = 0,
61284             ratio,
61285             gutterY = me.chart.maxGutter[1],
61286             ubbox, bbox, point, prevX, prevLabel,
61287             projectedWidth = 0,
61288             textLabel, attr, textRight, text,
61289             label, last, x, y, i, firstLabel;
61290
61291         last = ln - 1;
61292         //get a reference to the first text label dimensions
61293         point = inflections[0];
61294         firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
61295         ratio = Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)) >> 0;
61296         
61297         for (i = 0; i < ln; i++) {
61298             point = inflections[i];
61299             text = me.label.renderer(labels[i]);
61300             textLabel = me.getOrCreateLabel(i, text);
61301             bbox = textLabel._bbox;
61302             maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
61303             x = floor(point[0] - (ratio? bbox.height : bbox.width) / 2);
61304             if (me.chart.maxGutter[0] == 0) {
61305                 if (i == 0 && axes.findIndex('position', 'left') == -1) {
61306                     x = point[0];
61307                 }
61308                 else if (i == last && axes.findIndex('position', 'right') == -1) {
61309                     x = point[0] - bbox.width;
61310                 }
61311             }
61312             if (position == 'top') {
61313                 y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
61314             }
61315             else {
61316                 y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
61317             }
61318             
61319             textLabel.setAttributes({
61320                 hidden: false,
61321                 x: x,
61322                 y: y
61323             }, true);
61324
61325             // Skip label if there isn't available minimum space
61326             if (i != 0 && (me.intersect(textLabel, prevLabel)
61327                 || me.intersect(textLabel, firstLabel))) {
61328                 textLabel.hide(true);
61329                 continue;
61330             }
61331             
61332             prevLabel = textLabel;
61333         }
61334
61335         return maxHeight;
61336     },
61337     
61338     drawVerticalLabels: function() {
61339         var me = this,
61340             inflections = me.inflections,
61341             position = me.position,
61342             ln = inflections.length,
61343             labels = me.labels,
61344             maxWidth = 0,
61345             max = Math.max,
61346             floor = Math.floor,
61347             ceil = Math.ceil,
61348             axes = me.chart.axes,
61349             gutterY = me.chart.maxGutter[1],
61350             ubbox, bbox, point, prevLabel,
61351             projectedWidth = 0,
61352             textLabel, attr, textRight, text,
61353             label, last, x, y, i;
61354
61355         last = ln;
61356         for (i = 0; i < last; i++) {
61357             point = inflections[i];
61358             text = me.label.renderer(labels[i]);
61359             textLabel = me.getOrCreateLabel(i, text);
61360             bbox = textLabel._bbox;
61361             
61362             maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
61363             y = point[1];
61364             if (gutterY < bbox.height / 2) {
61365                 if (i == last - 1 && axes.findIndex('position', 'top') == -1) {
61366                     y = me.y - me.length + ceil(bbox.height / 2);
61367                 }
61368                 else if (i == 0 && axes.findIndex('position', 'bottom') == -1) {
61369                     y = me.y - floor(bbox.height / 2);
61370                 }
61371             }
61372             if (position == 'left') {
61373                 x = point[0] - bbox.width - me.dashSize - me.label.padding - 2;
61374             }
61375             else {
61376                 x = point[0] + me.dashSize + me.label.padding + 2;
61377             }    
61378             textLabel.setAttributes(Ext.apply({
61379                 hidden: false,
61380                 x: x,
61381                 y: y
61382             }, me.label), true);
61383             // Skip label if there isn't available minimum space
61384             if (i != 0 && me.intersect(textLabel, prevLabel)) {
61385                 textLabel.hide(true);
61386                 continue;
61387             }
61388             prevLabel = textLabel;
61389         }
61390         
61391         return maxWidth;
61392     },
61393
61394     /**
61395      * Renders the labels in the axes.
61396      */
61397     drawLabel: function() {
61398         var me = this,
61399             position = me.position,
61400             labelGroup = me.labelGroup,
61401             inflections = me.inflections,
61402             maxWidth = 0,
61403             maxHeight = 0,
61404             ln, i;
61405
61406         if (position == 'left' || position == 'right') {
61407             maxWidth = me.drawVerticalLabels();    
61408         } else {
61409             maxHeight = me.drawHorizontalLabels();
61410         }
61411
61412         // Hide unused bars
61413         ln = labelGroup.getCount();
61414         i = inflections.length;
61415         for (; i < ln; i++) {
61416             labelGroup.getAt(i).hide(true);
61417         }
61418
61419         me.bbox = {};
61420         Ext.apply(me.bbox, me.axisBBox);
61421         me.bbox.height = maxHeight;
61422         me.bbox.width = maxWidth;
61423         if (Ext.isString(me.title)) {
61424             me.drawTitle(maxWidth, maxHeight);
61425         }
61426     },
61427
61428     // @private creates the elipsis for the text.
61429     elipsis: function(sprite, text, desiredWidth, minWidth, center) {
61430         var bbox,
61431             x;
61432
61433         if (desiredWidth < minWidth) {
61434             sprite.hide(true);
61435             return false;
61436         }
61437         while (text.length > 4) {
61438             text = text.substr(0, text.length - 4) + "...";
61439             sprite.setAttributes({
61440                 text: text
61441             }, true);
61442             bbox = sprite.getBBox();
61443             if (bbox.width < desiredWidth) {
61444                 if (typeof center == 'number') {
61445                     sprite.setAttributes({
61446                         x: Math.floor(center - (bbox.width / 2))
61447                     }, true);
61448                 }
61449                 break;
61450             }
61451         }
61452         return true;
61453     },
61454
61455     /**
61456      * Updates the {@link #title} of this axis.
61457      * @param {String} title
61458      */
61459     setTitle: function(title) {
61460         this.title = title;
61461         this.drawLabel();
61462     },
61463
61464     // @private draws the title for the axis.
61465     drawTitle: function(maxWidth, maxHeight) {
61466         var me = this,
61467             position = me.position,
61468             surface = me.chart.surface,
61469             displaySprite = me.displaySprite,
61470             title = me.title,
61471             rotate = (position == 'left' || position == 'right'),
61472             x = me.x,
61473             y = me.y,
61474             base, bbox, pad;
61475
61476         if (displaySprite) {
61477             displaySprite.setAttributes({text: title}, true);
61478         } else {
61479             base = {
61480                 type: 'text',
61481                 x: 0,
61482                 y: 0,
61483                 text: title
61484             };
61485             displaySprite = me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle));
61486             surface.renderItem(displaySprite);
61487         }
61488         bbox = displaySprite.getBBox();
61489         pad = me.dashSize + me.label.padding;
61490
61491         if (rotate) {
61492             y -= ((me.length / 2) - (bbox.height / 2));
61493             if (position == 'left') {
61494                 x -= (maxWidth + pad + (bbox.width / 2));
61495             }
61496             else {
61497                 x += (maxWidth + pad + bbox.width - (bbox.width / 2));
61498             }
61499             me.bbox.width += bbox.width + 10;
61500         }
61501         else {
61502             x += (me.length / 2) - (bbox.width * 0.5);
61503             if (position == 'top') {
61504                 y -= (maxHeight + pad + (bbox.height * 0.3));
61505             }
61506             else {
61507                 y += (maxHeight + pad + (bbox.height * 0.8));
61508             }
61509             me.bbox.height += bbox.height + 10;
61510         }
61511         displaySprite.setAttributes({
61512             translate: {
61513                 x: x,
61514                 y: y
61515             }
61516         }, true);
61517     }
61518 });
61519 /**
61520  * @class Ext.chart.axis.Category
61521  * @extends Ext.chart.axis.Axis
61522  *
61523  * A type of axis that displays items in categories. This axis is generally used to
61524  * display categorical information like names of items, month names, quarters, etc.
61525  * but no quantitative values. For that other type of information <em>Number</em>
61526  * axis are more suitable.
61527  *
61528  * As with other axis you can set the position of the axis and its title. For example:
61529  *
61530  * {@img Ext.chart.axis.Category/Ext.chart.axis.Category.png Ext.chart.axis.Category chart axis}
61531  *
61532  *     var store = Ext.create('Ext.data.JsonStore', {
61533  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
61534  *         data: [
61535  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
61536  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
61537  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
61538  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
61539  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
61540  *         ]
61541  *     });
61542  *  
61543  *     Ext.create('Ext.chart.Chart', {
61544  *         renderTo: Ext.getBody(),
61545  *         width: 500,
61546  *         height: 300,
61547  *         store: store,
61548  *         axes: [{
61549  *             type: 'Numeric',
61550  *             grid: true,
61551  *             position: 'left',
61552  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
61553  *             title: 'Sample Values',
61554  *             grid: {
61555  *                 odd: {
61556  *                     opacity: 1,
61557  *                     fill: '#ddd',
61558  *                     stroke: '#bbb',
61559  *                     'stroke-width': 1
61560  *                 }
61561  *             },
61562  *             minimum: 0,
61563  *             adjustMinimumByMajorUnit: 0
61564  *         }, {
61565  *             type: 'Category',
61566  *             position: 'bottom',
61567  *             fields: ['name'],
61568  *             title: 'Sample Metrics',
61569  *             grid: true,
61570  *             label: {
61571  *                 rotate: {
61572  *                     degrees: 315
61573  *                 }
61574  *             }
61575  *         }],
61576  *         series: [{
61577  *             type: 'area',
61578  *             highlight: false,
61579  *             axis: 'left',
61580  *             xField: 'name',
61581  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
61582  *             style: {
61583  *                 opacity: 0.93
61584  *             }
61585  *         }]
61586  *     });
61587  *
61588  * In this example with set the category axis to the bottom of the surface, bound the axis to
61589  * the <em>name</em> property and set as title <em>Month of the Year</em>.
61590  */
61591
61592 Ext.define('Ext.chart.axis.Category', {
61593
61594     /* Begin Definitions */
61595
61596     extend: 'Ext.chart.axis.Axis',
61597
61598     alternateClassName: 'Ext.chart.CategoryAxis',
61599
61600     alias: 'axis.category',
61601
61602     /* End Definitions */
61603
61604     /**
61605      * A list of category names to display along this axis.
61606      *
61607      * @property categoryNames
61608      * @type Array
61609      */
61610     categoryNames: null,
61611
61612     /**
61613      * Indicates whether or not to calculate the number of categories (ticks and
61614      * labels) when there is not enough room to display all labels on the axis.
61615      * If set to true, the axis will determine the number of categories to plot.
61616      * If not, all categories will be plotted.
61617      *
61618      * @property calculateCategoryCount
61619      * @type Boolean
61620      */
61621     calculateCategoryCount: false,
61622
61623     // @private creates an array of labels to be used when rendering.
61624     setLabels: function() {
61625         var store = this.chart.store,
61626             fields = this.fields,
61627             ln = fields.length,
61628             i;
61629
61630         this.labels = [];
61631         store.each(function(record) {
61632             for (i = 0; i < ln; i++) {
61633                 this.labels.push(record.get(fields[i]));
61634             }
61635         }, this);
61636     },
61637
61638     // @private calculates labels positions and marker positions for rendering.
61639     applyData: function() {
61640         this.callParent();
61641         this.setLabels();
61642         var count = this.chart.store.getCount();
61643         return {
61644             from: 0,
61645             to: count,
61646             power: 1,
61647             step: 1,
61648             steps: count - 1
61649         };
61650     }
61651 });
61652
61653 /**
61654  * @class Ext.chart.axis.Gauge
61655  * @extends Ext.chart.axis.Abstract
61656  *
61657  * Gauge Axis is the axis to be used with a Gauge series. The Gauge axis
61658  * displays numeric data from an interval defined by the `minimum`, `maximum` and
61659  * `step` configuration properties. The placement of the numeric data can be changed
61660  * by altering the `margin` option that is set to `10` by default.
61661  *
61662  * A possible configuration for this axis would look like:
61663  *
61664  *     axes: [{
61665  *         type: 'gauge',
61666  *         position: 'gauge',
61667  *         minimum: 0,
61668  *         maximum: 100,
61669  *         steps: 10,
61670  *         margin: 7
61671  *     }],
61672  */
61673 Ext.define('Ext.chart.axis.Gauge', {
61674
61675     /* Begin Definitions */
61676
61677     extend: 'Ext.chart.axis.Abstract',
61678
61679     /* End Definitions */
61680     
61681     /**
61682      * @cfg {Number} minimum (required) the minimum value of the interval to be displayed in the axis.
61683      */
61684
61685     /**
61686      * @cfg {Number} maximum (required) the maximum value of the interval to be displayed in the axis.
61687      */
61688
61689     /**
61690      * @cfg {Number} steps (required) the number of steps and tick marks to add to the interval.
61691      */
61692
61693     /**
61694      * @cfg {Number} margin (optional) the offset positioning of the tick marks and labels in pixels. Default's 10.
61695      */
61696
61697     position: 'gauge',
61698
61699     alias: 'axis.gauge',
61700
61701     drawAxis: function(init) {
61702         var chart = this.chart,
61703             surface = chart.surface,
61704             bbox = chart.chartBBox,
61705             centerX = bbox.x + (bbox.width / 2),
61706             centerY = bbox.y + bbox.height,
61707             margin = this.margin || 10,
61708             rho = Math.min(bbox.width, 2 * bbox.height) /2 + margin,
61709             sprites = [], sprite,
61710             steps = this.steps,
61711             i, pi = Math.PI,
61712             cos = Math.cos,
61713             sin = Math.sin;
61714
61715         if (this.sprites && !chart.resizing) {
61716             this.drawLabel();
61717             return;
61718         }
61719
61720         if (this.margin >= 0) {
61721             if (!this.sprites) {
61722                 //draw circles
61723                 for (i = 0; i <= steps; i++) {
61724                     sprite = surface.add({
61725                         type: 'path',
61726                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
61727                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
61728                                     'L', centerX + rho * cos(i / steps * pi - pi),
61729                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
61730                         stroke: '#ccc'
61731                     });
61732                     sprite.setAttributes({
61733                         hidden: false
61734                     }, true);
61735                     sprites.push(sprite);
61736                 }
61737             } else {
61738                 sprites = this.sprites;
61739                 //draw circles
61740                 for (i = 0; i <= steps; i++) {
61741                     sprites[i].setAttributes({
61742                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
61743                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
61744                                'L', centerX + rho * cos(i / steps * pi - pi),
61745                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
61746                         stroke: '#ccc'
61747                     }, true);
61748                 }
61749             }
61750         }
61751         this.sprites = sprites;
61752         this.drawLabel();
61753         if (this.title) {
61754             this.drawTitle();
61755         }
61756     },
61757     
61758     drawTitle: function() {
61759         var me = this,
61760             chart = me.chart,
61761             surface = chart.surface,
61762             bbox = chart.chartBBox,
61763             labelSprite = me.titleSprite,
61764             labelBBox;
61765         
61766         if (!labelSprite) {
61767             me.titleSprite = labelSprite = surface.add({
61768                 type: 'text',
61769                 zIndex: 2
61770             });    
61771         }
61772         labelSprite.setAttributes(Ext.apply({
61773             text: me.title
61774         }, me.label || {}), true);
61775         labelBBox = labelSprite.getBBox();
61776         labelSprite.setAttributes({
61777             x: bbox.x + (bbox.width / 2) - (labelBBox.width / 2),
61778             y: bbox.y + bbox.height - (labelBBox.height / 2) - 4
61779         }, true);
61780     },
61781
61782     /**
61783      * Updates the {@link #title} of this axis.
61784      * @param {String} title
61785      */
61786     setTitle: function(title) {
61787         this.title = title;
61788         this.drawTitle();
61789     },
61790
61791     drawLabel: function() {
61792         var chart = this.chart,
61793             surface = chart.surface,
61794             bbox = chart.chartBBox,
61795             centerX = bbox.x + (bbox.width / 2),
61796             centerY = bbox.y + bbox.height,
61797             margin = this.margin || 10,
61798             rho = Math.min(bbox.width, 2 * bbox.height) /2 + 2 * margin,
61799             round = Math.round,
61800             labelArray = [], label,
61801             maxValue = this.maximum || 0,
61802             steps = this.steps, i = 0,
61803             adjY,
61804             pi = Math.PI,
61805             cos = Math.cos,
61806             sin = Math.sin,
61807             labelConf = this.label,
61808             renderer = labelConf.renderer || function(v) { return v; };
61809
61810         if (!this.labelArray) {
61811             //draw scale
61812             for (i = 0; i <= steps; i++) {
61813                 // TODO Adjust for height of text / 2 instead
61814                 adjY = (i === 0 || i === steps) ? 7 : 0;
61815                 label = surface.add({
61816                     type: 'text',
61817                     text: renderer(round(i / steps * maxValue)),
61818                     x: centerX + rho * cos(i / steps * pi - pi),
61819                     y: centerY + rho * sin(i / steps * pi - pi) - adjY,
61820                     'text-anchor': 'middle',
61821                     'stroke-width': 0.2,
61822                     zIndex: 10,
61823                     stroke: '#333'
61824                 });
61825                 label.setAttributes({
61826                     hidden: false
61827                 }, true);
61828                 labelArray.push(label);
61829             }
61830         }
61831         else {
61832             labelArray = this.labelArray;
61833             //draw values
61834             for (i = 0; i <= steps; i++) {
61835                 // TODO Adjust for height of text / 2 instead
61836                 adjY = (i === 0 || i === steps) ? 7 : 0;
61837                 labelArray[i].setAttributes({
61838                     text: renderer(round(i / steps * maxValue)),
61839                     x: centerX + rho * cos(i / steps * pi - pi),
61840                     y: centerY + rho * sin(i / steps * pi - pi) - adjY
61841                 }, true);
61842             }
61843         }
61844         this.labelArray = labelArray;
61845     }
61846 });
61847 /**
61848  * @class Ext.chart.axis.Numeric
61849  * @extends Ext.chart.axis.Axis
61850  *
61851  * An axis to handle numeric values. This axis is used for quantitative data as
61852  * opposed to the category axis. You can set mininum and maximum values to the
61853  * axis so that the values are bound to that. If no values are set, then the
61854  * scale will auto-adjust to the values.
61855  *
61856  * {@img Ext.chart.axis.Numeric/Ext.chart.axis.Numeric.png Ext.chart.axis.Numeric chart axis}
61857  *
61858  * For example:
61859  *
61860  *     var store = Ext.create('Ext.data.JsonStore', {
61861  *          fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
61862  *          data: [
61863  *              {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
61864  *              {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
61865  *              {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
61866  *              {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
61867  *              {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
61868  *          ]
61869  *     });
61870  *  
61871  *     Ext.create('Ext.chart.Chart', {
61872  *         renderTo: Ext.getBody(),
61873  *         width: 500,
61874  *         height: 300,
61875  *         store: store,
61876  *         axes: [{
61877  *             type: 'Numeric',
61878  *             grid: true,
61879  *             position: 'left',
61880  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
61881  *             title: 'Sample Values',
61882  *             grid: {
61883  *                 odd: {
61884  *                     opacity: 1,
61885  *                     fill: '#ddd',
61886  *                     stroke: '#bbb',
61887  *                     'stroke-width': 1
61888  *                 }
61889  *             },
61890  *             minimum: 0,
61891  *             adjustMinimumByMajorUnit: 0
61892  *         }, {
61893  *             type: 'Category',
61894  *             position: 'bottom',
61895  *             fields: ['name'],
61896  *             title: 'Sample Metrics',
61897  *             grid: true,
61898  *             label: {
61899  *                 rotate: {
61900  *                     degrees: 315
61901  *                 }
61902  *             }
61903  *         }],
61904  *         series: [{
61905  *             type: 'area',
61906  *             highlight: false,
61907  *             axis: 'left',
61908  *             xField: 'name',
61909  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
61910  *             style: {
61911  *                 opacity: 0.93
61912  *             }
61913  *         }]
61914  *     });
61915  *
61916  * In this example we create an axis of Numeric type. We set a minimum value so that
61917  * even if all series have values greater than zero, the grid starts at zero. We bind
61918  * the axis onto the left part of the surface by setting <em>position</em> to <em>left</em>.
61919  * We bind three different store fields to this axis by setting <em>fields</em> to an array.
61920  * We set the title of the axis to <em>Number of Hits</em> by using the <em>title</em> property.
61921  * We use a <em>grid</em> configuration to set odd background rows to a certain style and even rows
61922  * to be transparent/ignored.
61923  *
61924  * @constructor
61925  */
61926 Ext.define('Ext.chart.axis.Numeric', {
61927
61928     /* Begin Definitions */
61929
61930     extend: 'Ext.chart.axis.Axis',
61931
61932     alternateClassName: 'Ext.chart.NumericAxis',
61933
61934     /* End Definitions */
61935
61936     type: 'numeric',
61937
61938     alias: 'axis.numeric',
61939
61940     constructor: function(config) {
61941         var me = this, label, f;
61942         me.callParent([config]);
61943         label = me.label;
61944         if (me.roundToDecimal === false) {
61945             return;
61946         }
61947         if (label.renderer) {
61948             f = label.renderer;
61949             label.renderer = function(v) {
61950                 return me.roundToDecimal( f(v), me.decimals );
61951             };
61952         } else {
61953             label.renderer = function(v) {
61954                 return me.roundToDecimal(v, me.decimals);
61955             };
61956         }
61957     },
61958     
61959     roundToDecimal: function(v, dec) {
61960         var val = Math.pow(10, dec || 0);
61961         return ((v * val) >> 0) / val;
61962     },
61963     
61964     /**
61965      * The minimum value drawn by the axis. If not set explicitly, the axis
61966      * minimum will be calculated automatically.
61967      *
61968      * @property minimum
61969      * @type Number
61970      */
61971     minimum: NaN,
61972
61973     /**
61974      * The maximum value drawn by the axis. If not set explicitly, the axis
61975      * maximum will be calculated automatically.
61976      *
61977      * @property maximum
61978      * @type Number
61979      */
61980     maximum: NaN,
61981
61982     /**
61983      * The number of decimals to round the value to.
61984      * Default's 2.
61985      *
61986      * @property decimals
61987      * @type Number
61988      */
61989     decimals: 2,
61990
61991     /**
61992      * The scaling algorithm to use on this axis. May be "linear" or
61993      * "logarithmic".
61994      *
61995      * @property scale
61996      * @type String
61997      */
61998     scale: "linear",
61999
62000     /**
62001      * Indicates the position of the axis relative to the chart
62002      *
62003      * @property position
62004      * @type String
62005      */
62006     position: 'left',
62007
62008     /**
62009      * Indicates whether to extend maximum beyond data's maximum to the nearest
62010      * majorUnit.
62011      *
62012      * @property adjustMaximumByMajorUnit
62013      * @type Boolean
62014      */
62015     adjustMaximumByMajorUnit: false,
62016
62017     /**
62018      * Indicates whether to extend the minimum beyond data's minimum to the
62019      * nearest majorUnit.
62020      *
62021      * @property adjustMinimumByMajorUnit
62022      * @type Boolean
62023      */
62024     adjustMinimumByMajorUnit: false,
62025
62026     // @private apply data.
62027     applyData: function() {
62028         this.callParent();
62029         return this.calcEnds();
62030     }
62031 });
62032
62033 /**
62034  * @class Ext.chart.axis.Radial
62035  * @extends Ext.chart.axis.Abstract
62036  * @ignore
62037  */
62038 Ext.define('Ext.chart.axis.Radial', {
62039
62040     /* Begin Definitions */
62041
62042     extend: 'Ext.chart.axis.Abstract',
62043
62044     /* End Definitions */
62045
62046     position: 'radial',
62047
62048     alias: 'axis.radial',
62049
62050     drawAxis: function(init) {
62051         var chart = this.chart,
62052             surface = chart.surface,
62053             bbox = chart.chartBBox,
62054             store = chart.store,
62055             l = store.getCount(),
62056             centerX = bbox.x + (bbox.width / 2),
62057             centerY = bbox.y + (bbox.height / 2),
62058             rho = Math.min(bbox.width, bbox.height) /2,
62059             sprites = [], sprite,
62060             steps = this.steps,
62061             i, j, pi2 = Math.PI * 2,
62062             cos = Math.cos, sin = Math.sin;
62063
62064         if (this.sprites && !chart.resizing) {
62065             this.drawLabel();
62066             return;
62067         }
62068
62069         if (!this.sprites) {
62070             //draw circles
62071             for (i = 1; i <= steps; i++) {
62072                 sprite = surface.add({
62073                     type: 'circle',
62074                     x: centerX,
62075                     y: centerY,
62076                     radius: Math.max(rho * i / steps, 0),
62077                     stroke: '#ccc'
62078                 });
62079                 sprite.setAttributes({
62080                     hidden: false
62081                 }, true);
62082                 sprites.push(sprite);
62083             }
62084             //draw lines
62085             store.each(function(rec, i) {
62086                 sprite = surface.add({
62087                     type: 'path',
62088                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(i / l * pi2), centerY + rho * sin(i / l * pi2), 'Z'],
62089                     stroke: '#ccc'
62090                 });
62091                 sprite.setAttributes({
62092                     hidden: false
62093                 }, true);
62094                 sprites.push(sprite);
62095             });
62096         } else {
62097             sprites = this.sprites;
62098             //draw circles
62099             for (i = 0; i < steps; i++) {
62100                 sprites[i].setAttributes({
62101                     x: centerX,
62102                     y: centerY,
62103                     radius: Math.max(rho * (i + 1) / steps, 0),
62104                     stroke: '#ccc'
62105                 }, true);
62106             }
62107             //draw lines
62108             store.each(function(rec, j) {
62109                 sprites[i + j].setAttributes({
62110                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(j / l * pi2), centerY + rho * sin(j / l * pi2), 'Z'],
62111                     stroke: '#ccc'
62112                 }, true);
62113             });
62114         }
62115         this.sprites = sprites;
62116
62117         this.drawLabel();
62118     },
62119
62120     drawLabel: function() {
62121         var chart = this.chart,
62122             surface = chart.surface,
62123             bbox = chart.chartBBox,
62124             store = chart.store,
62125             centerX = bbox.x + (bbox.width / 2),
62126             centerY = bbox.y + (bbox.height / 2),
62127             rho = Math.min(bbox.width, bbox.height) /2,
62128             max = Math.max, round = Math.round,
62129             labelArray = [], label,
62130             fields = [], nfields,
62131             categories = [], xField,
62132             aggregate = !this.maximum,
62133             maxValue = this.maximum || 0,
62134             steps = this.steps, i = 0, j, dx, dy,
62135             pi2 = Math.PI * 2,
62136             cos = Math.cos, sin = Math.sin,
62137             display = this.label.display,
62138             draw = display !== 'none',
62139             margin = 10;
62140
62141         if (!draw) {
62142             return;
62143         }
62144
62145         //get all rendered fields
62146         chart.series.each(function(series) {
62147             fields.push(series.yField);
62148             xField = series.xField;
62149         });
62150         
62151         //get maxValue to interpolate
62152         store.each(function(record, i) {
62153             if (aggregate) {
62154                 for (i = 0, nfields = fields.length; i < nfields; i++) {
62155                     maxValue = max(+record.get(fields[i]), maxValue);
62156                 }
62157             }
62158             categories.push(record.get(xField));
62159         });
62160         if (!this.labelArray) {
62161             if (display != 'categories') {
62162                 //draw scale
62163                 for (i = 1; i <= steps; i++) {
62164                     label = surface.add({
62165                         type: 'text',
62166                         text: round(i / steps * maxValue),
62167                         x: centerX,
62168                         y: centerY - rho * i / steps,
62169                         'text-anchor': 'middle',
62170                         'stroke-width': 0.1,
62171                         stroke: '#333'
62172                     });
62173                     label.setAttributes({
62174                         hidden: false
62175                     }, true);
62176                     labelArray.push(label);
62177                 }
62178             }
62179             if (display != 'scale') {
62180                 //draw text
62181                 for (j = 0, steps = categories.length; j < steps; j++) {
62182                     dx = cos(j / steps * pi2) * (rho + margin);
62183                     dy = sin(j / steps * pi2) * (rho + margin);
62184                     label = surface.add({
62185                         type: 'text',
62186                         text: categories[j],
62187                         x: centerX + dx,
62188                         y: centerY + dy,
62189                         'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
62190                     });
62191                     label.setAttributes({
62192                         hidden: false
62193                     }, true);
62194                     labelArray.push(label);
62195                 }
62196             }
62197         }
62198         else {
62199             labelArray = this.labelArray;
62200             if (display != 'categories') {
62201                 //draw values
62202                 for (i = 0; i < steps; i++) {
62203                     labelArray[i].setAttributes({
62204                         text: round((i + 1) / steps * maxValue),
62205                         x: centerX,
62206                         y: centerY - rho * (i + 1) / steps,
62207                         'text-anchor': 'middle',
62208                         'stroke-width': 0.1,
62209                         stroke: '#333'
62210                     }, true);
62211                 }
62212             }
62213             if (display != 'scale') {
62214                 //draw text
62215                 for (j = 0, steps = categories.length; j < steps; j++) {
62216                     dx = cos(j / steps * pi2) * (rho + margin);
62217                     dy = sin(j / steps * pi2) * (rho + margin);
62218                     if (labelArray[i + j]) {
62219                         labelArray[i + j].setAttributes({
62220                             type: 'text',
62221                             text: categories[j],
62222                             x: centerX + dx,
62223                             y: centerY + dy,
62224                             'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
62225                         }, true);
62226                     }
62227                 }
62228             }
62229         }
62230         this.labelArray = labelArray;
62231     }
62232 });
62233 /**
62234  * @author Ed Spencer
62235  * @class Ext.data.AbstractStore
62236  *
62237  * <p>AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
62238  * but offers a set of methods used by both of those subclasses.</p>
62239  * 
62240  * <p>We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
62241  * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what 
62242  * AbstractStore is and is not.</p>
62243  * 
62244  * <p>AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be 
62245  * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a 
62246  * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.</p>
62247  * 
62248  * <p>AbstractStore provides a few helpful methods such as {@link #load} and {@link #sync}, which load and save data
62249  * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
62250  * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data - 
62251  * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
62252  * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.</p>
62253  * 
62254  * TODO: Update these docs to explain about the sortable and filterable mixins.
62255  * <p>Finally, AbstractStore provides an API for sorting and filtering data via its {@link #sorters} and {@link #filters}
62256  * {@link Ext.util.MixedCollection MixedCollections}. Although this functionality is provided by AbstractStore, there's a
62257  * good description of how to use it in the introduction of {@link Ext.data.Store}.
62258  * 
62259  */
62260 Ext.define('Ext.data.AbstractStore', {
62261     requires: ['Ext.util.MixedCollection', 'Ext.data.Operation', 'Ext.util.Filter'],
62262     
62263     mixins: {
62264         observable: 'Ext.util.Observable',
62265         sortable: 'Ext.util.Sortable'
62266     },
62267     
62268     statics: {
62269         create: function(store){
62270             if (!store.isStore) {
62271                 if (!store.type) {
62272                     store.type = 'store';
62273                 }
62274                 store = Ext.createByAlias('store.' + store.type, store);
62275             }
62276             return store;
62277         }    
62278     },
62279     
62280     remoteSort  : false,
62281     remoteFilter: false,
62282
62283     /**
62284      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
62285      * object or a Proxy instance - see {@link #setProxy} for details.
62286      */
62287
62288     /**
62289      * @cfg {Boolean/Object} autoLoad If data is not specified, and if autoLoad is true or an Object, this store's load method
62290      * is automatically called after creation. If the value of autoLoad is an Object, this Object will be passed to the store's
62291      * load method. Defaults to false.
62292      */
62293     autoLoad: false,
62294
62295     /**
62296      * @cfg {Boolean} autoSync True to automatically sync the Store with its Proxy after every edit to one of its Records.
62297      * Defaults to false.
62298      */
62299     autoSync: false,
62300
62301     /**
62302      * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
62303      * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
62304      * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
62305      * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
62306      * @property batchUpdateMode
62307      * @type String
62308      */
62309     batchUpdateMode: 'operation',
62310
62311     /**
62312      * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
62313      * Defaults to true, ignored if {@link #remoteFilter} is true
62314      * @property filterOnLoad
62315      * @type Boolean
62316      */
62317     filterOnLoad: true,
62318
62319     /**
62320      * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
62321      * Defaults to true, igored if {@link #remoteSort} is true
62322      * @property sortOnLoad
62323      * @type Boolean
62324      */
62325     sortOnLoad: true,
62326
62327     /**
62328      * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's constructor
62329      * instead of a model constructor or name.
62330      * @property implicitModel
62331      * @type Boolean
62332      * @private
62333      */
62334     implicitModel: false,
62335
62336     /**
62337      * The string type of the Proxy to create if none is specified. This defaults to creating a {@link Ext.data.proxy.Memory memory proxy}.
62338      * @property defaultProxyType
62339      * @type String
62340      */
62341     defaultProxyType: 'memory',
62342
62343     /**
62344      * True if the Store has already been destroyed via {@link #destroyStore}. If this is true, the reference to Store should be deleted
62345      * as it will not function correctly any more.
62346      * @property isDestroyed
62347      * @type Boolean
62348      */
62349     isDestroyed: false,
62350
62351     isStore: true,
62352
62353     /**
62354      * @cfg {String} storeId Optional unique identifier for this store. If present, this Store will be registered with 
62355      * the {@link Ext.data.StoreManager}, making it easy to reuse elsewhere. Defaults to undefined.
62356      */
62357     
62358     /**
62359      * @cfg {Array} fields
62360      * This may be used in place of specifying a {@link #model} configuration. The fields should be a 
62361      * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
62362      * with these fields. In general this configuration option should be avoided, it exists for the purposes of
62363      * backwards compatibility. For anything more complicated, such as specifying a particular id property or
62364      * assocations, a {@link Ext.data.Model} should be defined and specified for the {@link #model} config.
62365      */
62366
62367     sortRoot: 'data',
62368     
62369     //documented above
62370     constructor: function(config) {
62371         var me = this,
62372             filters;
62373         
62374         me.addEvents(
62375             /**
62376              * @event add
62377              * Fired when a Model instance has been added to this Store
62378              * @param {Ext.data.Store} store The store
62379              * @param {Array} records The Model instances that were added
62380              * @param {Number} index The index at which the instances were inserted
62381              */
62382             'add',
62383
62384             /**
62385              * @event remove
62386              * Fired when a Model instance has been removed from this Store
62387              * @param {Ext.data.Store} store The Store object
62388              * @param {Ext.data.Model} record The record that was removed
62389              * @param {Number} index The index of the record that was removed
62390              */
62391             'remove',
62392             
62393             /**
62394              * @event update
62395              * Fires when a Record has been updated
62396              * @param {Store} this
62397              * @param {Ext.data.Model} record The Model instance that was updated
62398              * @param {String} operation The update operation being performed. Value may be one of:
62399              * <pre><code>
62400                Ext.data.Model.EDIT
62401                Ext.data.Model.REJECT
62402                Ext.data.Model.COMMIT
62403              * </code></pre>
62404              */
62405             'update',
62406
62407             /**
62408              * @event datachanged
62409              * Fires whenever the records in the Store have changed in some way - this could include adding or removing records,
62410              * or updating the data in existing records
62411              * @param {Ext.data.Store} this The data store
62412              */
62413             'datachanged',
62414
62415             /**
62416              * @event beforeload
62417              * Event description
62418              * @param {Ext.data.Store} store This Store
62419              * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to load the Store
62420              */
62421             'beforeload',
62422
62423             /**
62424              * @event load
62425              * Fires whenever the store reads data from a remote data source.
62426              * @param {Ext.data.Store} this
62427              * @param {Array} records An array of records
62428              * @param {Boolean} successful True if the operation was successful.
62429              */
62430             'load',
62431
62432             /**
62433              * @event beforesync
62434              * Called before a call to {@link #sync} is executed. Return false from any listener to cancel the synv
62435              * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
62436              */
62437             'beforesync',
62438             /**
62439              * @event clear
62440              * Fired after the {@link #removeAll} method is called.
62441              * @param {Ext.data.Store} this
62442              */
62443             'clear'
62444         );
62445         
62446         Ext.apply(me, config);
62447         // don't use *config* anymore from here on... use *me* instead...
62448
62449         /**
62450          * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
62451          * at which point this is cleared.
62452          * @private
62453          * @property removed
62454          * @type Array
62455          */
62456         me.removed = [];
62457
62458         me.mixins.observable.constructor.apply(me, arguments);
62459         me.model = Ext.ModelManager.getModel(me.model);
62460
62461         /**
62462          * @property modelDefaults
62463          * @type Object
62464          * @private
62465          * A set of default values to be applied to every model instance added via {@link #insert} or created via {@link #create}.
62466          * This is used internally by associations to set foreign keys and other fields. See the Association classes source code
62467          * for examples. This should not need to be used by application developers.
62468          */
62469         Ext.applyIf(me, {
62470             modelDefaults: {}
62471         });
62472
62473         //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
62474         if (!me.model && me.fields) {
62475             me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
62476                 extend: 'Ext.data.Model',
62477                 fields: me.fields,
62478                 proxy: me.proxy || me.defaultProxyType
62479             });
62480
62481             delete me.fields;
62482
62483             me.implicitModel = true;
62484         }
62485
62486         //ensures that the Proxy is instantiated correctly
62487         me.setProxy(me.proxy || me.model.getProxy());
62488
62489         if (me.id && !me.storeId) {
62490             me.storeId = me.id;
62491             delete me.id;
62492         }
62493
62494         if (me.storeId) {
62495             Ext.data.StoreManager.register(me);
62496         }
62497         
62498         me.mixins.sortable.initSortable.call(me);        
62499         
62500         /**
62501          * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
62502          * @property filters
62503          * @type Ext.util.MixedCollection
62504          */
62505         filters = me.decodeFilters(me.filters);
62506         me.filters = Ext.create('Ext.util.MixedCollection');
62507         me.filters.addAll(filters);
62508     },
62509
62510     /**
62511      * Sets the Store's Proxy by string, config object or Proxy instance
62512      * @param {String|Object|Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
62513      * or an Ext.data.proxy.Proxy instance
62514      * @return {Ext.data.proxy.Proxy} The attached Proxy object
62515      */
62516     setProxy: function(proxy) {
62517         var me = this;
62518         
62519         if (proxy instanceof Ext.data.proxy.Proxy) {
62520             proxy.setModel(me.model);
62521         } else {
62522             if (Ext.isString(proxy)) {
62523                 proxy = {
62524                     type: proxy    
62525                 };
62526             }
62527             Ext.applyIf(proxy, {
62528                 model: me.model
62529             });
62530             
62531             proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
62532         }
62533         
62534         me.proxy = proxy;
62535         
62536         return me.proxy;
62537     },
62538
62539     /**
62540      * Returns the proxy currently attached to this proxy instance
62541      * @return {Ext.data.proxy.Proxy} The Proxy instance
62542      */
62543     getProxy: function() {
62544         return this.proxy;
62545     },
62546
62547     //saves any phantom records
62548     create: function(data, options) {
62549         var me = this,
62550             instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
62551             operation;
62552         
62553         options = options || {};
62554
62555         Ext.applyIf(options, {
62556             action : 'create',
62557             records: [instance]
62558         });
62559
62560         operation = Ext.create('Ext.data.Operation', options);
62561
62562         me.proxy.create(operation, me.onProxyWrite, me);
62563         
62564         return instance;
62565     },
62566
62567     read: function() {
62568         return this.load.apply(this, arguments);
62569     },
62570
62571     onProxyRead: Ext.emptyFn,
62572
62573     update: function(options) {
62574         var me = this,
62575             operation;
62576         options = options || {};
62577
62578         Ext.applyIf(options, {
62579             action : 'update',
62580             records: me.getUpdatedRecords()
62581         });
62582
62583         operation = Ext.create('Ext.data.Operation', options);
62584
62585         return me.proxy.update(operation, me.onProxyWrite, me);
62586     },
62587
62588     /**
62589      * @private
62590      * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
62591      * the updates provided by the Proxy
62592      */
62593     onProxyWrite: function(operation) {
62594         var me = this,
62595             success = operation.wasSuccessful(),
62596             records = operation.getRecords();
62597
62598         switch (operation.action) {
62599             case 'create':
62600                 me.onCreateRecords(records, operation, success);
62601                 break;
62602             case 'update':
62603                 me.onUpdateRecords(records, operation, success);
62604                 break;
62605             case 'destroy':
62606                 me.onDestroyRecords(records, operation, success);
62607                 break;
62608         }
62609
62610         if (success) {
62611             me.fireEvent('write', me, operation);
62612             me.fireEvent('datachanged', me);
62613         }
62614         //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
62615         Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
62616     },
62617
62618
62619     //tells the attached proxy to destroy the given records
62620     destroy: function(options) {
62621         var me = this,
62622             operation;
62623             
62624         options = options || {};
62625
62626         Ext.applyIf(options, {
62627             action : 'destroy',
62628             records: me.getRemovedRecords()
62629         });
62630
62631         operation = Ext.create('Ext.data.Operation', options);
62632
62633         return me.proxy.destroy(operation, me.onProxyWrite, me);
62634     },
62635
62636     /**
62637      * @private
62638      * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
62639      * to onProxyWrite.
62640      */
62641     onBatchOperationComplete: function(batch, operation) {
62642         return this.onProxyWrite(operation);
62643     },
62644
62645     /**
62646      * @private
62647      * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
62648      * and updates the Store's internal data MixedCollection.
62649      */
62650     onBatchComplete: function(batch, operation) {
62651         var me = this,
62652             operations = batch.operations,
62653             length = operations.length,
62654             i;
62655
62656         me.suspendEvents();
62657
62658         for (i = 0; i < length; i++) {
62659             me.onProxyWrite(operations[i]);
62660         }
62661
62662         me.resumeEvents();
62663
62664         me.fireEvent('datachanged', me);
62665     },
62666
62667     onBatchException: function(batch, operation) {
62668         // //decide what to do... could continue with the next operation
62669         // batch.start();
62670         //
62671         // //or retry the last operation
62672         // batch.retry();
62673     },
62674
62675     /**
62676      * @private
62677      * Filter function for new records.
62678      */
62679     filterNew: function(item) {
62680         // only want phantom records that are valid
62681         return item.phantom === true && item.isValid();
62682     },
62683
62684     /**
62685      * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
62686      * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
62687      * @return {Array} The Model instances
62688      */
62689     getNewRecords: function() {
62690         return [];
62691     },
62692
62693     /**
62694      * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
62695      * @return {Array} The updated Model instances
62696      */
62697     getUpdatedRecords: function() {
62698         return [];
62699     },
62700
62701     /**
62702      * @private
62703      * Filter function for updated records.
62704      */
62705     filterUpdated: function(item) {
62706         // only want dirty records, not phantoms that are valid
62707         return item.dirty === true && item.phantom !== true && item.isValid();
62708     },
62709
62710     /**
62711      * Returns any records that have been removed from the store but not yet destroyed on the proxy.
62712      * @return {Array} The removed Model instances
62713      */
62714     getRemovedRecords: function() {
62715         return this.removed;
62716     },
62717
62718     filter: function(filters, value) {
62719
62720     },
62721
62722     /**
62723      * @private
62724      * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
62725      * @param {Array} filters The filters array
62726      * @return {Array} Array of Ext.util.Filter objects
62727      */
62728     decodeFilters: function(filters) {
62729         if (!Ext.isArray(filters)) {
62730             if (filters === undefined) {
62731                 filters = [];
62732             } else {
62733                 filters = [filters];
62734             }
62735         }
62736
62737         var length = filters.length,
62738             Filter = Ext.util.Filter,
62739             config, i;
62740
62741         for (i = 0; i < length; i++) {
62742             config = filters[i];
62743
62744             if (!(config instanceof Filter)) {
62745                 Ext.apply(config, {
62746                     root: 'data'
62747                 });
62748
62749                 //support for 3.x style filters where a function can be defined as 'fn'
62750                 if (config.fn) {
62751                     config.filterFn = config.fn;
62752                 }
62753
62754                 //support a function to be passed as a filter definition
62755                 if (typeof config == 'function') {
62756                     config = {
62757                         filterFn: config
62758                     };
62759                 }
62760
62761                 filters[i] = new Filter(config);
62762             }
62763         }
62764
62765         return filters;
62766     },
62767
62768     clearFilter: function(supressEvent) {
62769
62770     },
62771
62772     isFiltered: function() {
62773
62774     },
62775
62776     filterBy: function(fn, scope) {
62777
62778     },
62779     
62780     /**
62781      * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
62782      * and deleted records in the store, updating the Store's internal representation of the records
62783      * as each operation completes.
62784      */
62785     sync: function() {
62786         var me        = this,
62787             options   = {},
62788             toCreate  = me.getNewRecords(),
62789             toUpdate  = me.getUpdatedRecords(),
62790             toDestroy = me.getRemovedRecords(),
62791             needsSync = false;
62792
62793         if (toCreate.length > 0) {
62794             options.create = toCreate;
62795             needsSync = true;
62796         }
62797
62798         if (toUpdate.length > 0) {
62799             options.update = toUpdate;
62800             needsSync = true;
62801         }
62802
62803         if (toDestroy.length > 0) {
62804             options.destroy = toDestroy;
62805             needsSync = true;
62806         }
62807
62808         if (needsSync && me.fireEvent('beforesync', options) !== false) {
62809             me.proxy.batch(options, me.getBatchListeners());
62810         }
62811     },
62812
62813
62814     /**
62815      * @private
62816      * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
62817      * This is broken out into a separate function to allow for customisation of the listeners
62818      * @return {Object} The listeners object
62819      */
62820     getBatchListeners: function() {
62821         var me = this,
62822             listeners = {
62823                 scope: me,
62824                 exception: me.onBatchException
62825             };
62826
62827         if (me.batchUpdateMode == 'operation') {
62828             listeners.operationcomplete = me.onBatchOperationComplete;
62829         } else {
62830             listeners.complete = me.onBatchComplete;
62831         }
62832
62833         return listeners;
62834     },
62835
62836     //deprecated, will be removed in 5.0
62837     save: function() {
62838         return this.sync.apply(this, arguments);
62839     },
62840
62841     /**
62842      * Loads the Store using its configured {@link #proxy}.
62843      * @param {Object} options Optional config object. This is passed into the {@link Ext.data.Operation Operation}
62844      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
62845      */
62846     load: function(options) {
62847         var me = this,
62848             operation;
62849
62850         options = options || {};
62851
62852         Ext.applyIf(options, {
62853             action : 'read',
62854             filters: me.filters.items,
62855             sorters: me.getSorters()
62856         });
62857         
62858         operation = Ext.create('Ext.data.Operation', options);
62859
62860         if (me.fireEvent('beforeload', me, operation) !== false) {
62861             me.loading = true;
62862             me.proxy.read(operation, me.onProxyLoad, me);
62863         }
62864         
62865         return me;
62866     },
62867
62868     /**
62869      * @private
62870      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
62871      * @param {Ext.data.Model} record The model instance that was edited
62872      */
62873     afterEdit : function(record) {
62874         var me = this;
62875         
62876         if (me.autoSync) {
62877             me.sync();
62878         }
62879         
62880         me.fireEvent('update', me, record, Ext.data.Model.EDIT);
62881     },
62882
62883     /**
62884      * @private
62885      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
62886      * @param {Ext.data.Model} record The model instance that was edited
62887      */
62888     afterReject : function(record) {
62889         this.fireEvent('update', this, record, Ext.data.Model.REJECT);
62890     },
62891
62892     /**
62893      * @private
62894      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
62895      * @param {Ext.data.Model} record The model instance that was edited
62896      */
62897     afterCommit : function(record) {
62898         this.fireEvent('update', this, record, Ext.data.Model.COMMIT);
62899     },
62900
62901     clearData: Ext.emptyFn,
62902
62903     destroyStore: function() {
62904         var me = this;
62905         
62906         if (!me.isDestroyed) {
62907             if (me.storeId) {
62908                 Ext.data.StoreManager.unregister(me);
62909             }
62910             me.clearData();
62911             me.data = null;
62912             me.tree = null;
62913             // Ext.destroy(this.proxy);
62914             me.reader = me.writer = null;
62915             me.clearListeners();
62916             me.isDestroyed = true;
62917
62918             if (me.implicitModel) {
62919                 Ext.destroy(me.model);
62920             }
62921         }
62922     },
62923     
62924     doSort: function(sorterFn) {
62925         var me = this;
62926         if (me.remoteSort) {
62927             //the load function will pick up the new sorters and request the sorted data from the proxy
62928             me.load();
62929         } else {
62930             me.data.sortBy(sorterFn);
62931             me.fireEvent('datachanged', me);
62932         }
62933     },
62934
62935     getCount: Ext.emptyFn,
62936
62937     getById: Ext.emptyFn,
62938     
62939     /**
62940      * Removes all records from the store. This method does a "fast remove",
62941      * individual remove events are not called. The {@link #clear} event is
62942      * fired upon completion.
62943      * @method
62944      */
62945     removeAll: Ext.emptyFn,
62946     // individual substores should implement a "fast" remove
62947     // and fire a clear event afterwards
62948
62949     /**
62950      * Returns true if the Store is currently performing a load operation
62951      * @return {Boolean} True if the Store is currently loading
62952      */
62953     isLoading: function() {
62954         return this.loading;
62955      }
62956 });
62957
62958 /**
62959  * @class Ext.util.Grouper
62960  * @extends Ext.util.Sorter
62961  */
62962  
62963 Ext.define('Ext.util.Grouper', {
62964
62965     /* Begin Definitions */
62966
62967     extend: 'Ext.util.Sorter',
62968
62969     /* End Definitions */
62970
62971     /**
62972      * Function description
62973      * @param {Ext.data.Model} instance The Model instance
62974      * @return {String} The group string for this model
62975      */
62976     getGroupString: function(instance) {
62977         return instance.get(this.property);
62978     }
62979 });
62980 /**
62981  * @author Ed Spencer
62982  * @class Ext.data.Store
62983  * @extends Ext.data.AbstractStore
62984  *
62985  * <p>The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
62986  * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
62987  * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.</p>
62988  *
62989  * <p>Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:</p>
62990  *
62991 <pre><code>
62992 // Set up a {@link Ext.data.Model model} to use in our Store
62993 Ext.define('User', {
62994     extend: 'Ext.data.Model',
62995     fields: [
62996         {name: 'firstName', type: 'string'},
62997         {name: 'lastName',  type: 'string'},
62998         {name: 'age',       type: 'int'},
62999         {name: 'eyeColor',  type: 'string'}
63000     ]
63001 });
63002
63003 var myStore = new Ext.data.Store({
63004     model: 'User',
63005     proxy: {
63006         type: 'ajax',
63007         url : '/users.json',
63008         reader: {
63009             type: 'json',
63010             root: 'users'
63011         }
63012     },
63013     autoLoad: true
63014 });
63015 </code></pre>
63016
63017  * <p>In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
63018  * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
63019  * {@link Ext.data.reader.Json see the docs on JsonReader} for details.</p>
63020  *
63021  * <p><u>Inline data</u></p>
63022  *
63023  * <p>Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #data}
63024  * into Model instances:</p>
63025  *
63026 <pre><code>
63027 new Ext.data.Store({
63028     model: 'User',
63029     data : [
63030         {firstName: 'Ed',    lastName: 'Spencer'},
63031         {firstName: 'Tommy', lastName: 'Maintz'},
63032         {firstName: 'Aaron', lastName: 'Conran'},
63033         {firstName: 'Jamie', lastName: 'Avins'}
63034     ]
63035 });
63036 </code></pre>
63037  *
63038  * <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
63039  * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
63040  * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).</p>
63041  *
63042  * <p>Additional data can also be loaded locally using {@link #add}.</p>
63043  *
63044  * <p><u>Loading Nested Data</u></p>
63045  *
63046  * <p>Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
63047  * 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
63048  * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
63049  * docs for a full explanation:</p>
63050  *
63051 <pre><code>
63052 var store = new Ext.data.Store({
63053     autoLoad: true,
63054     model: "User",
63055     proxy: {
63056         type: 'ajax',
63057         url : 'users.json',
63058         reader: {
63059             type: 'json',
63060             root: 'users'
63061         }
63062     }
63063 });
63064 </code></pre>
63065  *
63066  * <p>Which would consume a response like this:</p>
63067  *
63068 <pre><code>
63069 {
63070     "users": [
63071         {
63072             "id": 1,
63073             "name": "Ed",
63074             "orders": [
63075                 {
63076                     "id": 10,
63077                     "total": 10.76,
63078                     "status": "invoiced"
63079                 },
63080                 {
63081                     "id": 11,
63082                     "total": 13.45,
63083                     "status": "shipped"
63084                 }
63085             ]
63086         }
63087     ]
63088 }
63089 </code></pre>
63090  *
63091  * <p>See the {@link Ext.data.reader.Reader} intro docs for a full explanation.</p>
63092  *
63093  * <p><u>Filtering and Sorting</u></p>
63094  *
63095  * <p>Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
63096  * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
63097  * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
63098  *
63099 <pre><code>
63100 var store = new Ext.data.Store({
63101     model: 'User',
63102     sorters: [
63103         {
63104             property : 'age',
63105             direction: 'DESC'
63106         },
63107         {
63108             property : 'firstName',
63109             direction: 'ASC'
63110         }
63111     ],
63112
63113     filters: [
63114         {
63115             property: 'firstName',
63116             value   : /Ed/
63117         }
63118     ]
63119 });
63120 </code></pre>
63121  *
63122  * <p>The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
63123  * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
63124  * perform these operations instead.</p>
63125  *
63126  * <p>Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
63127  * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
63128  * default {@link #sortOnFilter} is set to true, which means that your sorters are automatically reapplied if using local sorting.</p>
63129  *
63130 <pre><code>
63131 store.filter('eyeColor', 'Brown');
63132 </code></pre>
63133  *
63134  * <p>Change the sorting at any time by calling {@link #sort}:</p>
63135  *
63136 <pre><code>
63137 store.sort('height', 'ASC');
63138 </code></pre>
63139  *
63140  * <p>Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
63141  * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
63142  * to the MixedCollection:</p>
63143  *
63144 <pre><code>
63145 store.sorters.add(new Ext.util.Sorter({
63146     property : 'shoeSize',
63147     direction: 'ASC'
63148 }));
63149
63150 store.sort();
63151 </code></pre>
63152  *
63153  * <p><u>Registering with StoreManager</u></p>
63154  *
63155  * <p>Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link Ext.data.StoreManager StoreManager}.
63156  * This makes it easy to reuse the same store in multiple views:</p>
63157  *
63158  <pre><code>
63159 //this store can be used several times
63160 new Ext.data.Store({
63161     model: 'User',
63162     storeId: 'usersStore'
63163 });
63164
63165 new Ext.List({
63166     store: 'usersStore',
63167
63168     //other config goes here
63169 });
63170
63171 new Ext.view.View({
63172     store: 'usersStore',
63173
63174     //other config goes here
63175 });
63176 </code></pre>
63177  *
63178  * <p><u>Further Reading</u></p>
63179  *
63180  * <p>Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
63181  * pieces and how they fit together, see:</p>
63182  *
63183  * <ul style="list-style-type: disc; padding-left: 25px">
63184  * <li>{@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used</li>
63185  * <li>{@link Ext.data.Model Model} - the core class in the data package</li>
63186  * <li>{@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response</li>
63187  * </ul>
63188  *
63189  * @constructor
63190  * @param {Object} config Optional config object
63191  */
63192 Ext.define('Ext.data.Store', {
63193     extend: 'Ext.data.AbstractStore',
63194
63195     alias: 'store.store',
63196
63197     requires: ['Ext.ModelManager', 'Ext.data.Model', 'Ext.util.Grouper'],
63198     uses: ['Ext.data.proxy.Memory'],
63199
63200     /**
63201      * @cfg {Boolean} remoteSort
63202      * True to defer any sorting operation to the server. If false, sorting is done locally on the client. Defaults to <tt>false</tt>.
63203      */
63204     remoteSort: false,
63205
63206     /**
63207      * @cfg {Boolean} remoteFilter
63208      * True to defer any filtering operation to the server. If false, filtering is done locally on the client. Defaults to <tt>false</tt>.
63209      */
63210     remoteFilter: false,
63211     
63212     /**
63213      * @cfg {Boolean} remoteGroup
63214      * True if the grouping should apply on the server side, false if it is local only (defaults to false).  If the
63215      * grouping is local, it can be applied immediately to the data.  If it is remote, then it will simply act as a
63216      * helper, automatically sending the grouping information to the server.
63217      */
63218     remoteGroup : false,
63219
63220     /**
63221      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
63222      * object or a Proxy instance - see {@link #setProxy} for details.
63223      */
63224
63225     /**
63226      * @cfg {Array} data Optional array of Model instances or data objects to load locally. See "Inline data" above for details.
63227      */
63228
63229     /**
63230      * @cfg {String} model The {@link Ext.data.Model} associated with this store
63231      */
63232
63233     /**
63234      * The (optional) field by which to group data in the store. Internally, grouping is very similar to sorting - the
63235      * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
63236      * level of grouping, and groups can be fetched via the {@link #getGroups} method.
63237      * @property groupField
63238      * @type String
63239      */
63240     groupField: undefined,
63241
63242     /**
63243      * The direction in which sorting should be applied when grouping. Defaults to "ASC" - the other supported value is "DESC"
63244      * @property groupDir
63245      * @type String
63246      */
63247     groupDir: "ASC",
63248
63249     /**
63250      * @cfg {Number} pageSize
63251      * The number of records considered to form a 'page'. This is used to power the built-in
63252      * paging using the nextPage and previousPage functions. Defaults to 25.
63253      */
63254     pageSize: 25,
63255
63256     /**
63257      * The page that the Store has most recently loaded (see {@link #loadPage})
63258      * @property currentPage
63259      * @type Number
63260      */
63261     currentPage: 1,
63262
63263     /**
63264      * @cfg {Boolean} clearOnPageLoad True to empty the store when loading another page via {@link #loadPage},
63265      * {@link #nextPage} or {@link #previousPage} (defaults to true). Setting to false keeps existing records, allowing
63266      * large data sets to be loaded one page at a time but rendered all together.
63267      */
63268     clearOnPageLoad: true,
63269
63270     /**
63271      * True if the Store is currently loading via its Proxy
63272      * @property loading
63273      * @type Boolean
63274      * @private
63275      */
63276     loading: false,
63277
63278     /**
63279      * @cfg {Boolean} sortOnFilter For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
63280      * causing the sorters to be reapplied after filtering. Defaults to true
63281      */
63282     sortOnFilter: true,
63283     
63284     /**
63285      * @cfg {Boolean} buffered
63286      * Allow the store to buffer and pre-fetch pages of records. This is to be used in conjunction with a view will
63287      * tell the store to pre-fetch records ahead of a time.
63288      */
63289     buffered: false,
63290     
63291     /**
63292      * @cfg {Number} purgePageCount 
63293      * The number of pages to keep in the cache before purging additional records. A value of 0 indicates to never purge the prefetched data.
63294      * This option is only relevant when the {@link #buffered} option is set to true.
63295      */
63296     purgePageCount: 5,
63297
63298     isStore: true,
63299
63300     //documented above
63301     constructor: function(config) {
63302         config = config || {};
63303
63304         var me = this,
63305             groupers = config.groupers || me.groupers,
63306             groupField = config.groupField || me.groupField,
63307             proxy,
63308             data;
63309             
63310         if (config.buffered || me.buffered) {
63311             me.prefetchData = Ext.create('Ext.util.MixedCollection', false, function(record) {
63312                 return record.index;
63313             });
63314             me.pendingRequests = [];
63315             me.pagesRequested = [];
63316             
63317             me.sortOnLoad = false;
63318             me.filterOnLoad = false;
63319         }
63320             
63321         me.addEvents(
63322             /**
63323              * @event beforeprefetch
63324              * Fires before a prefetch occurs. Return false to cancel.
63325              * @param {Ext.data.store} this
63326              * @param {Ext.data.Operation} operation The associated operation
63327              */
63328             'beforeprefetch',
63329             /**
63330              * @event groupchange
63331              * Fired whenever the grouping in the grid changes
63332              * @param {Ext.data.Store} store The store
63333              * @param {Array} groupers The array of grouper objects
63334              */
63335             'groupchange',
63336             /**
63337              * @event load
63338              * Fires whenever records have been prefetched
63339              * @param {Ext.data.store} this
63340              * @param {Array} records An array of records
63341              * @param {Boolean} successful True if the operation was successful.
63342              * @param {Ext.data.Operation} operation The associated operation
63343              */
63344             'prefetch'
63345         );
63346         data = config.data || me.data;
63347
63348         /**
63349          * The MixedCollection that holds this store's local cache of records
63350          * @property data
63351          * @type Ext.util.MixedCollection
63352          */
63353         me.data = Ext.create('Ext.util.MixedCollection', false, function(record) {
63354             return record.internalId;
63355         });
63356
63357         if (data) {
63358             me.inlineData = data;
63359             delete config.data;
63360         }
63361         
63362         if (!groupers && groupField) {
63363             groupers = [{
63364                 property : groupField,
63365                 direction: config.groupDir || me.groupDir
63366             }];
63367         }
63368         delete config.groupers;
63369         
63370         /**
63371          * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store
63372          * @property groupers
63373          * @type Ext.util.MixedCollection
63374          */
63375         me.groupers = Ext.create('Ext.util.MixedCollection');
63376         me.groupers.addAll(me.decodeGroupers(groupers));
63377
63378         this.callParent([config]);
63379         // don't use *config* anymore from here on... use *me* instead...
63380         
63381         if (me.groupers.items.length) {
63382             me.sort(me.groupers.items, 'prepend', false);
63383         }
63384
63385         proxy = me.proxy;
63386         data = me.inlineData;
63387
63388         if (data) {
63389             if (proxy instanceof Ext.data.proxy.Memory) {
63390                 proxy.data = data;
63391                 me.read();
63392             } else {
63393                 me.add.apply(me, data);
63394             }
63395
63396             me.sort();
63397             delete me.inlineData;
63398         } else if (me.autoLoad) {
63399             Ext.defer(me.load, 10, me, [typeof me.autoLoad === 'object' ? me.autoLoad: undefined]);
63400             // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
63401             // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
63402         }
63403     },
63404     
63405     onBeforeSort: function() {
63406         this.sort(this.groupers.items, 'prepend', false);
63407     },
63408     
63409     /**
63410      * @private
63411      * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
63412      * @param {Array} groupers The groupers array
63413      * @return {Array} Array of Ext.util.Grouper objects
63414      */
63415     decodeGroupers: function(groupers) {
63416         if (!Ext.isArray(groupers)) {
63417             if (groupers === undefined) {
63418                 groupers = [];
63419             } else {
63420                 groupers = [groupers];
63421             }
63422         }
63423
63424         var length  = groupers.length,
63425             Grouper = Ext.util.Grouper,
63426             config, i;
63427
63428         for (i = 0; i < length; i++) {
63429             config = groupers[i];
63430
63431             if (!(config instanceof Grouper)) {
63432                 if (Ext.isString(config)) {
63433                     config = {
63434                         property: config
63435                     };
63436                 }
63437                 
63438                 Ext.applyIf(config, {
63439                     root     : 'data',
63440                     direction: "ASC"
63441                 });
63442
63443                 //support for 3.x style sorters where a function can be defined as 'fn'
63444                 if (config.fn) {
63445                     config.sorterFn = config.fn;
63446                 }
63447
63448                 //support a function to be passed as a sorter definition
63449                 if (typeof config == 'function') {
63450                     config = {
63451                         sorterFn: config
63452                     };
63453                 }
63454
63455                 groupers[i] = new Grouper(config);
63456             }
63457         }
63458
63459         return groupers;
63460     },
63461     
63462     /**
63463      * Group data in the store
63464      * @param {String|Array} groupers Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
63465      * or an Array of grouper configurations.
63466      * @param {String} direction The overall direction to group the data by. Defaults to "ASC".
63467      */
63468     group: function(groupers, direction) {
63469         var me = this,
63470             grouper,
63471             newGroupers;
63472             
63473         if (Ext.isArray(groupers)) {
63474             newGroupers = groupers;
63475         } else if (Ext.isObject(groupers)) {
63476             newGroupers = [groupers];
63477         } else if (Ext.isString(groupers)) {
63478             grouper = me.groupers.get(groupers);
63479
63480             if (!grouper) {
63481                 grouper = {
63482                     property : groupers,
63483                     direction: direction
63484                 };
63485                 newGroupers = [grouper];
63486             } else if (direction === undefined) {
63487                 grouper.toggle();
63488             } else {
63489                 grouper.setDirection(direction);
63490             }
63491         }
63492         
63493         if (newGroupers && newGroupers.length) {
63494             newGroupers = me.decodeGroupers(newGroupers);
63495             me.groupers.clear();
63496             me.groupers.addAll(newGroupers);
63497         }
63498         
63499         if (me.remoteGroup) {
63500             me.load({
63501                 scope: me,
63502                 callback: me.fireGroupChange
63503             });
63504         } else {
63505             me.sort();
63506             me.fireEvent('groupchange', me, me.groupers);
63507         }
63508     },
63509     
63510     /**
63511      * Clear any groupers in the store
63512      */
63513     clearGrouping: function(){
63514         var me = this;
63515         // Clear any groupers we pushed on to the sorters
63516         me.groupers.each(function(grouper){
63517             me.sorters.remove(grouper);
63518         });
63519         me.groupers.clear();
63520         if (me.remoteGroup) {
63521             me.load({
63522                 scope: me,
63523                 callback: me.fireGroupChange
63524             });
63525         } else {
63526             me.sort();
63527             me.fireEvent('groupchange', me, me.groupers);
63528         }
63529     },
63530     
63531     /**
63532      * Checks if the store is currently grouped
63533      * @return {Boolean} True if the store is grouped.
63534      */
63535     isGrouped: function() {
63536         return this.groupers.getCount() > 0;    
63537     },
63538     
63539     /**
63540      * Fires the groupchange event. Abstracted out so we can use it
63541      * as a callback
63542      * @private
63543      */
63544     fireGroupChange: function(){
63545         this.fireEvent('groupchange', this, this.groupers);    
63546     },
63547
63548     /**
63549      * Returns an object containing the result of applying grouping to the records in this store. See {@link #groupField},
63550      * {@link #groupDir} and {@link #getGroupString}. Example for a store containing records with a color field:
63551 <pre><code>
63552 var myStore = new Ext.data.Store({
63553     groupField: 'color',
63554     groupDir  : 'DESC'
63555 });
63556
63557 myStore.getGroups(); //returns:
63558 [
63559     {
63560         name: 'yellow',
63561         children: [
63562             //all records where the color field is 'yellow'
63563         ]
63564     },
63565     {
63566         name: 'red',
63567         children: [
63568             //all records where the color field is 'red'
63569         ]
63570     }
63571 ]
63572 </code></pre>
63573      * @param {String} groupName (Optional) Pass in an optional groupName argument to access a specific group as defined by {@link #getGroupString}
63574      * @return {Array} The grouped data
63575      */
63576     getGroups: function(requestGroupString) {
63577         var records = this.data.items,
63578             length = records.length,
63579             groups = [],
63580             pointers = {},
63581             record,
63582             groupStr,
63583             group,
63584             i;
63585
63586         for (i = 0; i < length; i++) {
63587             record = records[i];
63588             groupStr = this.getGroupString(record);
63589             group = pointers[groupStr];
63590
63591             if (group === undefined) {
63592                 group = {
63593                     name: groupStr,
63594                     children: []
63595                 };
63596
63597                 groups.push(group);
63598                 pointers[groupStr] = group;
63599             }
63600
63601             group.children.push(record);
63602         }
63603
63604         return requestGroupString ? pointers[requestGroupString] : groups;
63605     },
63606
63607     /**
63608      * @private
63609      * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
63610      * matching a certain group.
63611      */
63612     getGroupsForGrouper: function(records, grouper) {
63613         var length = records.length,
63614             groups = [],
63615             oldValue,
63616             newValue,
63617             record,
63618             group,
63619             i;
63620
63621         for (i = 0; i < length; i++) {
63622             record = records[i];
63623             newValue = grouper.getGroupString(record);
63624
63625             if (newValue !== oldValue) {
63626                 group = {
63627                     name: newValue,
63628                     grouper: grouper,
63629                     records: []
63630                 };
63631                 groups.push(group);
63632             }
63633
63634             group.records.push(record);
63635
63636             oldValue = newValue;
63637         }
63638
63639         return groups;
63640     },
63641
63642     /**
63643      * @private
63644      * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
63645      * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
63646      * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
63647      * @param {Array} records The set or subset of records to group
63648      * @param {Number} grouperIndex The grouper index to retrieve
63649      * @return {Array} The grouped records
63650      */
63651     getGroupsForGrouperIndex: function(records, grouperIndex) {
63652         var me = this,
63653             groupers = me.groupers,
63654             grouper = groupers.getAt(grouperIndex),
63655             groups = me.getGroupsForGrouper(records, grouper),
63656             length = groups.length,
63657             i;
63658
63659         if (grouperIndex + 1 < groupers.length) {
63660             for (i = 0; i < length; i++) {
63661                 groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
63662             }
63663         }
63664
63665         for (i = 0; i < length; i++) {
63666             groups[i].depth = grouperIndex;
63667         }
63668
63669         return groups;
63670     },
63671
63672     /**
63673      * @private
63674      * <p>Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
63675      * this case grouping by genre and then author in a fictional books dataset):</p>
63676 <pre><code>
63677 [
63678     {
63679         name: 'Fantasy',
63680         depth: 0,
63681         records: [
63682             //book1, book2, book3, book4
63683         ],
63684         children: [
63685             {
63686                 name: 'Rowling',
63687                 depth: 1,
63688                 records: [
63689                     //book1, book2
63690                 ]
63691             },
63692             {
63693                 name: 'Tolkein',
63694                 depth: 1,
63695                 records: [
63696                     //book3, book4
63697                 ]
63698             }
63699         ]
63700     }
63701 ]
63702 </code></pre>
63703      * @param {Boolean} sort True to call {@link #sort} before finding groups. Sorting is required to make grouping
63704      * function correctly so this should only be set to false if the Store is known to already be sorted correctly
63705      * (defaults to true)
63706      * @return {Array} The group data
63707      */
63708     getGroupData: function(sort) {
63709         var me = this;
63710         if (sort !== false) {
63711             me.sort();
63712         }
63713
63714         return me.getGroupsForGrouperIndex(me.data.items, 0);
63715     },
63716
63717     /**
63718      * <p>Returns the string to group on for a given model instance. The default implementation of this method returns
63719      * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
63720      * group by the first letter of a model's 'name' field, use the following code:</p>
63721 <pre><code>
63722 new Ext.data.Store({
63723     groupDir: 'ASC',
63724     getGroupString: function(instance) {
63725         return instance.get('name')[0];
63726     }
63727 });
63728 </code></pre>
63729      * @param {Ext.data.Model} instance The model instance
63730      * @return {String} The string to compare when forming groups
63731      */
63732     getGroupString: function(instance) {
63733         var group = this.groupers.first();
63734         if (group) {
63735             return instance.get(group.property);
63736         }
63737         return '';
63738     },
63739     /**
63740      * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
63741      * See also <code>{@link #add}</code>.
63742      * @param {Number} index The start index at which to insert the passed Records.
63743      * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
63744      */
63745     insert: function(index, records) {
63746         var me = this,
63747             sync = false,
63748             i,
63749             record,
63750             len;
63751
63752         records = [].concat(records);
63753         for (i = 0, len = records.length; i < len; i++) {
63754             record = me.createModel(records[i]);
63755             record.set(me.modelDefaults);
63756             // reassign the model in the array in case it wasn't created yet
63757             records[i] = record;
63758             
63759             me.data.insert(index + i, record);
63760             record.join(me);
63761
63762             sync = sync || record.phantom === true;
63763         }
63764
63765         if (me.snapshot) {
63766             me.snapshot.addAll(records);
63767         }
63768
63769         me.fireEvent('add', me, records, index);
63770         me.fireEvent('datachanged', me);
63771         if (me.autoSync && sync) {
63772             me.sync();
63773         }
63774     },
63775
63776     /**
63777      * Adds Model instances to the Store by instantiating them based on a JavaScript object. When adding already-
63778      * instantiated Models, use {@link #insert} instead. The instances will be added at the end of the existing collection.
63779      * This method accepts either a single argument array of Model instances or any number of model instance arguments.
63780      * Sample usage:
63781      *
63782 <pre><code>
63783 myStore.add({some: 'data'}, {some: 'other data'});
63784 </code></pre>
63785      *
63786      * @param {Object} data The data for each model
63787      * @return {Array} The array of newly created model instances
63788      */
63789     add: function(records) {
63790         //accept both a single-argument array of records, or any number of record arguments
63791         if (!Ext.isArray(records)) {
63792             records = Array.prototype.slice.apply(arguments);
63793         }
63794
63795         var me = this,
63796             i = 0,
63797             length = records.length,
63798             record;
63799
63800         for (; i < length; i++) {
63801             record = me.createModel(records[i]);
63802             // reassign the model in the array in case it wasn't created yet
63803             records[i] = record;
63804         }
63805
63806         me.insert(me.data.length, records);
63807
63808         return records;
63809     },
63810
63811     /**
63812      * Converts a literal to a model, if it's not a model already
63813      * @private
63814      * @param record {Ext.data.Model/Object} The record to create
63815      * @return {Ext.data.Model}
63816      */
63817     createModel: function(record) {
63818         if (!record.isModel) {
63819             record = Ext.ModelManager.create(record, this.model);
63820         }
63821
63822         return record;
63823     },
63824
63825     /**
63826      * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
63827      * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
63828      * Returning <tt>false</tt> aborts and exits the iteration.
63829      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
63830      * Defaults to the current {@link Ext.data.Model Record} in the iteration.
63831      */
63832     each: function(fn, scope) {
63833         this.data.each(fn, scope);
63834     },
63835
63836     /**
63837      * Removes the given record from the Store, firing the 'remove' event for each instance that is removed, plus a single
63838      * 'datachanged' event after removal.
63839      * @param {Ext.data.Model/Array} records The Ext.data.Model instance or array of instances to remove
63840      */
63841     remove: function(records, /* private */ isMove) {
63842         if (!Ext.isArray(records)) {
63843             records = [records];
63844         }
63845
63846         /*
63847          * Pass the isMove parameter if we know we're going to be re-inserting this record
63848          */
63849         isMove = isMove === true;
63850         var me = this,
63851             sync = false,
63852             i = 0,
63853             length = records.length,
63854             isPhantom,
63855             index,
63856             record;
63857
63858         for (; i < length; i++) {
63859             record = records[i];
63860             index = me.data.indexOf(record);
63861             
63862             if (me.snapshot) {
63863                 me.snapshot.remove(record);
63864             }
63865             
63866             if (index > -1) {
63867                 isPhantom = record.phantom === true;
63868                 if (!isMove && !isPhantom) {
63869                     // don't push phantom records onto removed
63870                     me.removed.push(record);
63871                 }
63872
63873                 record.unjoin(me);
63874                 me.data.remove(record);
63875                 sync = sync || !isPhantom;
63876
63877                 me.fireEvent('remove', me, record, index);
63878             }
63879         }
63880
63881         me.fireEvent('datachanged', me);
63882         if (!isMove && me.autoSync && sync) {
63883             me.sync();
63884         }
63885     },
63886
63887     /**
63888      * Removes the model instance at the given index
63889      * @param {Number} index The record index
63890      */
63891     removeAt: function(index) {
63892         var record = this.getAt(index);
63893
63894         if (record) {
63895             this.remove(record);
63896         }
63897     },
63898
63899     /**
63900      * <p>Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
63901      * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
63902      * instances into the Store and calling an optional callback if required. Example usage:</p>
63903      *
63904 <pre><code>
63905 store.load({
63906     scope   : this,
63907     callback: function(records, operation, success) {
63908         //the {@link Ext.data.Operation operation} object contains all of the details of the load operation
63909         console.log(records);
63910     }
63911 });
63912 </code></pre>
63913      *
63914      * <p>If the callback scope does not need to be set, a function can simply be passed:</p>
63915      *
63916 <pre><code>
63917 store.load(function(records, operation, success) {
63918     console.log('loaded records');
63919 });
63920 </code></pre>
63921      *
63922      * @param {Object/Function} options Optional config object, passed into the Ext.data.Operation object before loading.
63923      */
63924     load: function(options) {
63925         var me = this;
63926             
63927         options = options || {};
63928
63929         if (Ext.isFunction(options)) {
63930             options = {
63931                 callback: options
63932             };
63933         }
63934
63935         Ext.applyIf(options, {
63936             groupers: me.groupers.items,
63937             page: me.currentPage,
63938             start: (me.currentPage - 1) * me.pageSize,
63939             limit: me.pageSize,
63940             addRecords: false
63941         });      
63942
63943         return me.callParent([options]);
63944     },
63945
63946     /**
63947      * @private
63948      * Called internally when a Proxy has completed a load request
63949      */
63950     onProxyLoad: function(operation) {
63951         var me = this,
63952             resultSet = operation.getResultSet(),
63953             records = operation.getRecords(),
63954             successful = operation.wasSuccessful();
63955
63956         if (resultSet) {
63957             me.totalCount = resultSet.total;
63958         }
63959
63960         if (successful) {
63961             me.loadRecords(records, operation);
63962         }
63963
63964         me.loading = false;
63965         me.fireEvent('load', me, records, successful);
63966
63967         //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
63968         //People are definitely using this so can't deprecate safely until 2.x
63969         me.fireEvent('read', me, records, operation.wasSuccessful());
63970
63971         //this is a callback that would have been passed to the 'read' function and is optional
63972         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
63973     },
63974     
63975     /**
63976      * Create any new records when a write is returned from the server.
63977      * @private
63978      * @param {Array} records The array of new records
63979      * @param {Ext.data.Operation} operation The operation that just completed
63980      * @param {Boolean} success True if the operation was successful
63981      */
63982     onCreateRecords: function(records, operation, success) {
63983         if (success) {
63984             var i = 0,
63985                 data = this.data,
63986                 snapshot = this.snapshot,
63987                 length = records.length,
63988                 originalRecords = operation.records,
63989                 record,
63990                 original,
63991                 index;
63992
63993             /*
63994              * Loop over each record returned from the server. Assume they are
63995              * returned in order of how they were sent. If we find a matching
63996              * record, replace it with the newly created one.
63997              */
63998             for (; i < length; ++i) {
63999                 record = records[i];
64000                 original = originalRecords[i];
64001                 if (original) {
64002                     index = data.indexOf(original);
64003                     if (index > -1) {
64004                         data.removeAt(index);
64005                         data.insert(index, record);
64006                     }
64007                     if (snapshot) {
64008                         index = snapshot.indexOf(original);
64009                         if (index > -1) {
64010                             snapshot.removeAt(index);
64011                             snapshot.insert(index, record);
64012                         }
64013                     }
64014                     record.phantom = false;
64015                     record.join(this);
64016                 }
64017             }
64018         }
64019     },
64020
64021     /**
64022      * Update any records when a write is returned from the server.
64023      * @private
64024      * @param {Array} records The array of updated records
64025      * @param {Ext.data.Operation} operation The operation that just completed
64026      * @param {Boolean} success True if the operation was successful
64027      */
64028     onUpdateRecords: function(records, operation, success){
64029         if (success) {
64030             var i = 0,
64031                 length = records.length,
64032                 data = this.data,
64033                 snapshot = this.snapshot,
64034                 record;
64035
64036             for (; i < length; ++i) {
64037                 record = records[i];
64038                 data.replace(record);
64039                 if (snapshot) {
64040                     snapshot.replace(record);
64041                 }
64042                 record.join(this);
64043             }
64044         }
64045     },
64046
64047     /**
64048      * Remove any records when a write is returned from the server.
64049      * @private
64050      * @param {Array} records The array of removed records
64051      * @param {Ext.data.Operation} operation The operation that just completed
64052      * @param {Boolean} success True if the operation was successful
64053      */
64054     onDestroyRecords: function(records, operation, success){
64055         if (success) {
64056             var me = this,
64057                 i = 0,
64058                 length = records.length,
64059                 data = me.data,
64060                 snapshot = me.snapshot,
64061                 record;
64062
64063             for (; i < length; ++i) {
64064                 record = records[i];
64065                 record.unjoin(me);
64066                 data.remove(record);
64067                 if (snapshot) {
64068                     snapshot.remove(record);
64069                 }
64070             }
64071             me.removed = [];
64072         }
64073     },
64074
64075     //inherit docs
64076     getNewRecords: function() {
64077         return this.data.filterBy(this.filterNew).items;
64078     },
64079
64080     //inherit docs
64081     getUpdatedRecords: function() {
64082         return this.data.filterBy(this.filterUpdated).items;
64083     },
64084
64085     /**
64086      * Filters the loaded set of records by a given set of filters.
64087      * @param {Mixed} filters The set of filters to apply to the data. These are stored internally on the store,
64088      * but the filtering itself is done on the Store's {@link Ext.util.MixedCollection MixedCollection}. See
64089      * MixedCollection's {@link Ext.util.MixedCollection#filter filter} method for filter syntax. Alternatively,
64090      * pass in a property string
64091      * @param {String} value Optional value to filter by (only if using a property string as the first argument)
64092      */
64093     filter: function(filters, value) {
64094         if (Ext.isString(filters)) {
64095             filters = {
64096                 property: filters,
64097                 value: value
64098             };
64099         }
64100
64101         var me = this,
64102             decoded = me.decodeFilters(filters),
64103             i = 0,
64104             doLocalSort = me.sortOnFilter && !me.remoteSort,
64105             length = decoded.length;
64106
64107         for (; i < length; i++) {
64108             me.filters.replace(decoded[i]);
64109         }
64110
64111         if (me.remoteFilter) {
64112             //the load function will pick up the new filters and request the filtered data from the proxy
64113             me.load();
64114         } else {
64115             /**
64116              * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
64117              * records when a filter is removed or changed
64118              * @property snapshot
64119              * @type Ext.util.MixedCollection
64120              */
64121             if (me.filters.getCount()) {
64122                 me.snapshot = me.snapshot || me.data.clone();
64123                 me.data = me.data.filter(me.filters.items);
64124
64125                 if (doLocalSort) {
64126                     me.sort();
64127                 }
64128                 // fire datachanged event if it hasn't already been fired by doSort
64129                 if (!doLocalSort || me.sorters.length < 1) {
64130                     me.fireEvent('datachanged', me);
64131                 }
64132             }
64133         }
64134     },
64135
64136     /**
64137      * Revert to a view of the Record cache with no filtering applied.
64138      * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
64139      * {@link #datachanged} event.
64140      */
64141     clearFilter: function(suppressEvent) {
64142         var me = this;
64143
64144         me.filters.clear();
64145
64146         if (me.remoteFilter) {
64147             me.load();
64148         } else if (me.isFiltered()) {
64149             me.data = me.snapshot.clone();
64150             delete me.snapshot;
64151
64152             if (suppressEvent !== true) {
64153                 me.fireEvent('datachanged', me);
64154             }
64155         }
64156     },
64157
64158     /**
64159      * Returns true if this store is currently filtered
64160      * @return {Boolean}
64161      */
64162     isFiltered: function() {
64163         var snapshot = this.snapshot;
64164         return !! snapshot && snapshot !== this.data;
64165     },
64166
64167     /**
64168      * Filter by a function. The specified function will be called for each
64169      * Record in this Store. If the function returns <tt>true</tt> the Record is included,
64170      * otherwise it is filtered out.
64171      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
64172      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
64173      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
64174      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
64175      * </ul>
64176      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
64177      */
64178     filterBy: function(fn, scope) {
64179         var me = this;
64180
64181         me.snapshot = me.snapshot || me.data.clone();
64182         me.data = me.queryBy(fn, scope || me);
64183         me.fireEvent('datachanged', me);
64184     },
64185
64186     /**
64187      * Query the cached records in this Store using a filtering function. The specified function
64188      * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
64189      * included in the results.
64190      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
64191      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
64192      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
64193      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
64194      * </ul>
64195      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
64196      * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
64197      **/
64198     queryBy: function(fn, scope) {
64199         var me = this,
64200         data = me.snapshot || me.data;
64201         return data.filterBy(fn, scope || me);
64202     },
64203
64204     /**
64205      * Loads an array of data straight into the Store
64206      * @param {Array} data Array of data to load. Any non-model instances will be cast into model instances first
64207      * @param {Boolean} append True to add the records to the existing records in the store, false to remove the old ones first
64208      */
64209     loadData: function(data, append) {
64210         var model = this.model,
64211             length = data.length,
64212             i,
64213             record;
64214
64215         //make sure each data element is an Ext.data.Model instance
64216         for (i = 0; i < length; i++) {
64217             record = data[i];
64218
64219             if (! (record instanceof Ext.data.Model)) {
64220                 data[i] = Ext.ModelManager.create(record, model);
64221             }
64222         }
64223
64224         this.loadRecords(data, {addRecords: append});
64225     },
64226
64227     /**
64228      * Loads an array of {@Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
64229      * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #add} instead
64230      * @param {Array} records The array of records to load
64231      * @param {Object} options {addRecords: true} to add these records to the existing records, false to remove the Store's existing records first
64232      */
64233     loadRecords: function(records, options) {
64234         var me     = this,
64235             i      = 0,
64236             length = records.length;
64237
64238         options = options || {};
64239
64240
64241         if (!options.addRecords) {
64242             delete me.snapshot;
64243             me.data.clear();
64244         }
64245
64246         me.data.addAll(records);
64247
64248         //FIXME: this is not a good solution. Ed Spencer is totally responsible for this and should be forced to fix it immediately.
64249         for (; i < length; i++) {
64250             if (options.start !== undefined) {
64251                 records[i].index = options.start + i;
64252
64253             }
64254             records[i].join(me);
64255         }
64256
64257         /*
64258          * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
64259          * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
64260          * datachanged event is fired by the call to this.add, above.
64261          */
64262         me.suspendEvents();
64263
64264         if (me.filterOnLoad && !me.remoteFilter) {
64265             me.filter();
64266         }
64267
64268         if (me.sortOnLoad && !me.remoteSort) {
64269             me.sort();
64270         }
64271
64272         me.resumeEvents();
64273         me.fireEvent('datachanged', me, records);
64274     },
64275
64276     // PAGING METHODS
64277     /**
64278      * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
64279      * load operation, passing in calculated 'start' and 'limit' params
64280      * @param {Number} page The number of the page to load
64281      */
64282     loadPage: function(page) {
64283         var me = this;
64284
64285         me.currentPage = page;
64286
64287         me.read({
64288             page: page,
64289             start: (page - 1) * me.pageSize,
64290             limit: me.pageSize,
64291             addRecords: !me.clearOnPageLoad
64292         });
64293     },
64294
64295     /**
64296      * Loads the next 'page' in the current data set
64297      */
64298     nextPage: function() {
64299         this.loadPage(this.currentPage + 1);
64300     },
64301
64302     /**
64303      * Loads the previous 'page' in the current data set
64304      */
64305     previousPage: function() {
64306         this.loadPage(this.currentPage - 1);
64307     },
64308
64309     // private
64310     clearData: function() {
64311         this.data.each(function(record) {
64312             record.unjoin();
64313         });
64314
64315         this.data.clear();
64316     },
64317     
64318     // Buffering
64319     /**
64320      * Prefetches data the Store using its configured {@link #proxy}.
64321      * @param {Object} options Optional config object, passed into the Ext.data.Operation object before loading.
64322      * See {@link #load}
64323      */
64324     prefetch: function(options) {
64325         var me = this,
64326             operation,
64327             requestId = me.getRequestId();
64328
64329         options = options || {};
64330
64331         Ext.applyIf(options, {
64332             action : 'read',
64333             filters: me.filters.items,
64334             sorters: me.sorters.items,
64335             requestId: requestId
64336         });
64337         me.pendingRequests.push(requestId);
64338
64339         operation = Ext.create('Ext.data.Operation', options);
64340
64341         // HACK to implement loadMask support.
64342         //if (operation.blocking) {
64343         //    me.fireEvent('beforeload', me, operation);
64344         //}
64345         if (me.fireEvent('beforeprefetch', me, operation) !== false) {
64346             me.loading = true;
64347             me.proxy.read(operation, me.onProxyPrefetch, me);
64348         }
64349         
64350         return me;
64351     },
64352     
64353     /**
64354      * Prefetches a page of data.
64355      * @param {Number} page The page to prefetch
64356      * @param {Object} options Optional config object, passed into the Ext.data.Operation object before loading.
64357      * See {@link #load}
64358      * @param
64359      */
64360     prefetchPage: function(page, options) {
64361         var me = this,
64362             pageSize = me.pageSize,
64363             start = (page - 1) * me.pageSize,
64364             end = start + pageSize;
64365         
64366         // Currently not requesting this page and range isn't already satisified 
64367         if (Ext.Array.indexOf(me.pagesRequested, page) === -1 && !me.rangeSatisfied(start, end)) {
64368             options = options || {};
64369             me.pagesRequested.push(page);
64370             Ext.applyIf(options, {
64371                 page : page,
64372                 start: start,
64373                 limit: pageSize,
64374                 callback: me.onWaitForGuarantee,
64375                 scope: me
64376             });
64377             
64378             me.prefetch(options);
64379         }
64380         
64381     },
64382     
64383     /**
64384      * Returns a unique requestId to track requests.
64385      * @private
64386      */
64387     getRequestId: function() {
64388         this.requestSeed = this.requestSeed || 1;
64389         return this.requestSeed++;
64390     },
64391     
64392     /**
64393      * Handles a success pre-fetch
64394      * @private
64395      * @param {Ext.data.Operation} operation The operation that completed
64396      */
64397     onProxyPrefetch: function(operation) {
64398         var me         = this,
64399             resultSet  = operation.getResultSet(),
64400             records    = operation.getRecords(),
64401             
64402             successful = operation.wasSuccessful();
64403         
64404         if (resultSet) {
64405             me.totalCount = resultSet.total;
64406             me.fireEvent('totalcountchange', me.totalCount);
64407         }
64408         
64409         if (successful) {
64410             me.cacheRecords(records, operation);
64411         }
64412         Ext.Array.remove(me.pendingRequests, operation.requestId);
64413         if (operation.page) {
64414             Ext.Array.remove(me.pagesRequested, operation.page);
64415         }
64416         
64417         me.loading = false;
64418         me.fireEvent('prefetch', me, records, successful, operation);
64419         
64420         // HACK to support loadMask
64421         if (operation.blocking) {
64422             me.fireEvent('load', me, records, successful);
64423         }
64424
64425         //this is a callback that would have been passed to the 'read' function and is optional
64426         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
64427     },
64428     
64429     /**
64430      * Caches the records in the prefetch and stripes them with their server-side
64431      * index.
64432      * @private
64433      * @param {Array} records The records to cache
64434      * @param {Ext.data.Operation} The associated operation
64435      */
64436     cacheRecords: function(records, operation) {
64437         var me     = this,
64438             i      = 0,
64439             length = records.length,
64440             start  = operation ? operation.start : 0;
64441         
64442         if (!Ext.isDefined(me.totalCount)) {
64443             me.totalCount = records.length;
64444             me.fireEvent('totalcountchange', me.totalCount);
64445         }
64446         
64447         for (; i < length; i++) {
64448             // this is the true index, not the viewIndex
64449             records[i].index = start + i;
64450         }
64451         
64452         me.prefetchData.addAll(records);
64453         if (me.purgePageCount) {
64454             me.purgeRecords();
64455         }
64456         
64457     },
64458     
64459     
64460     /**
64461      * Purge the least recently used records in the prefetch if the purgeCount
64462      * has been exceeded.
64463      */
64464     purgeRecords: function() {
64465         var me = this,
64466             prefetchCount = me.prefetchData.getCount(),
64467             purgeCount = me.purgePageCount * me.pageSize,
64468             numRecordsToPurge = prefetchCount - purgeCount - 1,
64469             i = 0;
64470
64471         for (; i <= numRecordsToPurge; i++) {
64472             me.prefetchData.removeAt(0);
64473         }
64474     },
64475     
64476     /**
64477      * Determines if the range has already been satisfied in the prefetchData.
64478      * @private
64479      * @param {Number} start The start index
64480      * @param {Number} end The end index in the range
64481      */
64482     rangeSatisfied: function(start, end) {
64483         var me = this,
64484             i = start,
64485             satisfied = true;
64486
64487         for (; i < end; i++) {
64488             if (!me.prefetchData.getByKey(i)) {
64489                 satisfied = false;
64490                 if (end - i > me.pageSize) {
64491                     Ext.Error.raise("A single page prefetch could never satisfy this request.");
64492                 }
64493                 break;
64494             }
64495         }
64496         return satisfied;
64497     },
64498     
64499     /**
64500      * Determines the page from a record index
64501      * @param {Number} index The record index
64502      * @return {Number} The page the record belongs to
64503      */
64504     getPageFromRecordIndex: function(index) {
64505         return Math.floor(index / this.pageSize) + 1;
64506     },
64507     
64508     /**
64509      * Handles a guaranteed range being loaded
64510      * @private
64511      */
64512     onGuaranteedRange: function() {
64513         var me = this,
64514             totalCount = me.getTotalCount(),
64515             start = me.requestStart,
64516             end = ((totalCount - 1) < me.requestEnd) ? totalCount - 1 : me.requestEnd,
64517             range = [],
64518             record,
64519             i = start;
64520             
64521         if (start > end) {
64522             Ext.Error.raise("Start (" + start + ") was greater than end (" + end + ")");
64523         }
64524         
64525         if (start !== me.guaranteedStart && end !== me.guaranteedEnd) {
64526             me.guaranteedStart = start;
64527             me.guaranteedEnd = end;
64528             
64529             for (; i <= end; i++) {
64530                 record = me.prefetchData.getByKey(i);
64531                 if (!record) {
64532                     Ext.Error.raise("Record was not found and store said it was guaranteed");
64533                 }
64534                 range.push(record);
64535             }
64536             me.fireEvent('guaranteedrange', range, start, end);
64537             if (me.cb) {
64538                 me.cb.call(me.scope || me, range);
64539             }
64540         }
64541         
64542         me.unmask();
64543     },
64544     
64545     // hack to support loadmask
64546     mask: function() {
64547         this.masked = true;
64548         this.fireEvent('beforeload');
64549     },
64550     
64551     // hack to support loadmask
64552     unmask: function() {
64553         if (this.masked) {
64554             this.fireEvent('load');
64555         }
64556     },
64557     
64558     /**
64559      * Returns the number of pending requests out.
64560      */
64561     hasPendingRequests: function() {
64562         return this.pendingRequests.length;
64563     },
64564     
64565     
64566     // wait until all requests finish, until guaranteeing the range.
64567     onWaitForGuarantee: function() {
64568         if (!this.hasPendingRequests()) {
64569             this.onGuaranteedRange();
64570         }
64571     },
64572     
64573     /**
64574      * Guarantee a specific range, this will load the store with a range (that
64575      * must be the pageSize or smaller) and take care of any loading that may
64576      * be necessary.
64577      */
64578     guaranteeRange: function(start, end, cb, scope) {
64579         if (start && end) {
64580             if (end - start > this.pageSize) {
64581                 Ext.Error.raise({
64582                     start: start,
64583                     end: end,
64584                     pageSize: this.pageSize,
64585                     msg: "Requested a bigger range than the specified pageSize"
64586                 });
64587             }
64588         }
64589         
64590         end = (end > this.totalCount) ? this.totalCount - 1 : end;
64591         
64592         var me = this,
64593             i = start,
64594             prefetchData = me.prefetchData,
64595             range = [],
64596             startLoaded = !!prefetchData.getByKey(start),
64597             endLoaded = !!prefetchData.getByKey(end),
64598             startPage = me.getPageFromRecordIndex(start),
64599             endPage = me.getPageFromRecordIndex(end);
64600             
64601         me.cb = cb;
64602         me.scope = scope;
64603
64604         me.requestStart = start;
64605         me.requestEnd = end;
64606         // neither beginning or end are loaded
64607         if (!startLoaded || !endLoaded) {
64608             // same page, lets load it
64609             if (startPage === endPage) {
64610                 me.mask();
64611                 me.prefetchPage(startPage, {
64612                     //blocking: true,
64613                     callback: me.onWaitForGuarantee,
64614                     scope: me
64615                 });
64616             // need to load two pages
64617             } else {
64618                 me.mask();
64619                 me.prefetchPage(startPage, {
64620                     //blocking: true,
64621                     callback: me.onWaitForGuarantee,
64622                     scope: me
64623                 });
64624                 me.prefetchPage(endPage, {
64625                     //blocking: true,
64626                     callback: me.onWaitForGuarantee,
64627                     scope: me
64628                 });
64629             }
64630         // Request was already satisfied via the prefetch
64631         } else {
64632             me.onGuaranteedRange();
64633         }
64634     },
64635     
64636     // because prefetchData is stored by index
64637     // this invalidates all of the prefetchedData
64638     sort: function() {
64639         var me = this,
64640             prefetchData = me.prefetchData,
64641             sorters,
64642             start,
64643             end,
64644             range;
64645             
64646         if (me.buffered) {
64647             if (me.remoteSort) {
64648                 prefetchData.clear();
64649                 me.callParent(arguments);
64650             } else {
64651                 sorters = me.getSorters();
64652                 start = me.guaranteedStart;
64653                 end = me.guaranteedEnd;
64654                 
64655                 if (sorters.length) {
64656                     prefetchData.sort(sorters);
64657                     range = prefetchData.getRange();
64658                     prefetchData.clear();
64659                     me.cacheRecords(range);
64660                     delete me.guaranteedStart;
64661                     delete me.guaranteedEnd;
64662                     me.guaranteeRange(start, end);
64663                 }
64664                 me.callParent(arguments);
64665             }
64666         } else {
64667             me.callParent(arguments);
64668         }
64669     },
64670
64671     // overriden to provide striping of the indexes as sorting occurs.
64672     // this cannot be done inside of sort because datachanged has already
64673     // fired and will trigger a repaint of the bound view.
64674     doSort: function(sorterFn) {
64675         var me = this;
64676         if (me.remoteSort) {
64677             //the load function will pick up the new sorters and request the sorted data from the proxy
64678             me.load();
64679         } else {
64680             me.data.sortBy(sorterFn);
64681             if (!me.buffered) {
64682                 var range = me.getRange(),
64683                     ln = range.length,
64684                     i  = 0;
64685                 for (; i < ln; i++) {
64686                     range[i].index = i;
64687                 }
64688             }
64689             me.fireEvent('datachanged', me);
64690         }
64691     },
64692     
64693     /**
64694      * Finds the index of the first matching Record in this store by a specific field value.
64695      * @param {String} fieldName The name of the Record field to test.
64696      * @param {String/RegExp} value Either a string that the field value
64697      * should begin with, or a RegExp to test against the field.
64698      * @param {Number} startIndex (optional) The index to start searching at
64699      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
64700      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
64701      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false.
64702      * @return {Number} The matched index or -1
64703      */
64704     find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
64705         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
64706         return fn ? this.data.findIndexBy(fn, null, start) : -1;
64707     },
64708
64709     /**
64710      * Finds the first matching Record in this store by a specific field value.
64711      * @param {String} fieldName The name of the Record field to test.
64712      * @param {String/RegExp} value Either a string that the field value
64713      * should begin with, or a RegExp to test against the field.
64714      * @param {Number} startIndex (optional) The index to start searching at
64715      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
64716      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
64717      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false.
64718      * @return {Ext.data.Model} The matched record or null
64719      */
64720     findRecord: function() {
64721         var me = this,
64722             index = me.find.apply(me, arguments);
64723         return index !== -1 ? me.getAt(index) : null;
64724     },
64725
64726     /**
64727      * @private
64728      * Returns a filter function used to test a the given property's value. Defers most of the work to
64729      * Ext.util.MixedCollection's createValueMatcher function
64730      * @param {String} property The property to create the filter function for
64731      * @param {String/RegExp} value The string/regex to compare the property value to
64732      * @param {Boolean} anyMatch True if we don't care if the filter value is not the full value (defaults to false)
64733      * @param {Boolean} caseSensitive True to create a case-sensitive regex (defaults to false)
64734      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false.
64735      * Ignored if anyMatch is true.
64736      */
64737     createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
64738         if (Ext.isEmpty(value)) {
64739             return false;
64740         }
64741         value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
64742         return function(r) {
64743             return value.test(r.data[property]);
64744         };
64745     },
64746
64747     /**
64748      * Finds the index of the first matching Record in this store by a specific field value.
64749      * @param {String} fieldName The name of the Record field to test.
64750      * @param {Mixed} value The value to match the field against.
64751      * @param {Number} startIndex (optional) The index to start searching at
64752      * @return {Number} The matched index or -1
64753      */
64754     findExact: function(property, value, start) {
64755         return this.data.findIndexBy(function(rec) {
64756             return rec.get(property) === value;
64757         },
64758         this, start);
64759     },
64760
64761     /**
64762      * Find the index of the first matching Record in this Store by a function.
64763      * If the function returns <tt>true</tt> it is considered a match.
64764      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
64765      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
64766      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
64767      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
64768      * </ul>
64769      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
64770      * @param {Number} startIndex (optional) The index to start searching at
64771      * @return {Number} The matched index or -1
64772      */
64773     findBy: function(fn, scope, start) {
64774         return this.data.findIndexBy(fn, scope, start);
64775     },
64776
64777     /**
64778      * Collects unique values for a particular dataIndex from this store.
64779      * @param {String} dataIndex The property to collect
64780      * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
64781      * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
64782      * @return {Array} An array of the unique values
64783      **/
64784     collect: function(dataIndex, allowNull, bypassFilter) {
64785         var me = this,
64786             data = (bypassFilter === true && me.snapshot) ? me.snapshot: me.data;
64787
64788         return data.collect(dataIndex, 'data', allowNull);
64789     },
64790
64791     /**
64792      * Gets the number of cached records.
64793      * <p>If using paging, this may not be the total size of the dataset. If the data object
64794      * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
64795      * the dataset size.  <b>Note</b>: see the Important note in {@link #load}.</p>
64796      * @return {Number} The number of Records in the Store's cache.
64797      */
64798     getCount: function() {
64799         return this.data.length || 0;
64800     },
64801
64802     /**
64803      * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
64804      * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
64805      * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
64806      * could be loaded into the Store if the Store contained all data
64807      * @return {Number} The total number of Model instances available via the Proxy
64808      */
64809     getTotalCount: function() {
64810         return this.totalCount;
64811     },
64812
64813     /**
64814      * Get the Record at the specified index.
64815      * @param {Number} index The index of the Record to find.
64816      * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
64817      */
64818     getAt: function(index) {
64819         return this.data.getAt(index);
64820     },
64821
64822     /**
64823      * Returns a range of Records between specified indices.
64824      * @param {Number} startIndex (optional) The starting index (defaults to 0)
64825      * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
64826      * @return {Ext.data.Model[]} An array of Records
64827      */
64828     getRange: function(start, end) {
64829         return this.data.getRange(start, end);
64830     },
64831
64832     /**
64833      * Get the Record with the specified id.
64834      * @param {String} id The id of the Record to find.
64835      * @return {Ext.data.Model} The Record with the passed id. Returns undefined if not found.
64836      */
64837     getById: function(id) {
64838         return (this.snapshot || this.data).findBy(function(record) {
64839             return record.getId() === id;
64840         });
64841     },
64842
64843     /**
64844      * Get the index within the cache of the passed Record.
64845      * @param {Ext.data.Model} record The Ext.data.Model object to find.
64846      * @return {Number} The index of the passed Record. Returns -1 if not found.
64847      */
64848     indexOf: function(record) {
64849         return this.data.indexOf(record);
64850     },
64851
64852
64853     /**
64854      * Get the index within the entire dataset. From 0 to the totalCount.
64855      * @param {Ext.data.Model} record The Ext.data.Model object to find.
64856      * @return {Number} The index of the passed Record. Returns -1 if not found.
64857      */
64858     indexOfTotal: function(record) {
64859         return record.index || this.indexOf(record);
64860     },
64861
64862     /**
64863      * Get the index within the cache of the Record with the passed id.
64864      * @param {String} id The id of the Record to find.
64865      * @return {Number} The index of the Record. Returns -1 if not found.
64866      */
64867     indexOfId: function(id) {
64868         return this.data.indexOfKey(id);
64869     },
64870         
64871     /**
64872      * Remove all items from the store.
64873      * @param {Boolean} silent Prevent the `clear` event from being fired.
64874      */
64875     removeAll: function(silent) {
64876         var me = this;
64877
64878         me.clearData();
64879         if (me.snapshot) {
64880             me.snapshot.clear();
64881         }
64882         if (silent !== true) {
64883             me.fireEvent('clear', me);
64884         }
64885     },
64886
64887     /*
64888      * Aggregation methods
64889      */
64890
64891     /**
64892      * Convenience function for getting the first model instance in the store
64893      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64894      * in the store. The value returned will be an object literal with the key being the group
64895      * name and the first record being the value. The grouped parameter is only honored if
64896      * the store has a groupField.
64897      * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
64898      */
64899     first: function(grouped) {
64900         var me = this;
64901
64902         if (grouped && me.isGrouped()) {
64903             return me.aggregate(function(records) {
64904                 return records.length ? records[0] : undefined;
64905             }, me, true);
64906         } else {
64907             return me.data.first();
64908         }
64909     },
64910
64911     /**
64912      * Convenience function for getting the last model instance in the store
64913      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64914      * in the store. The value returned will be an object literal with the key being the group
64915      * name and the last record being the value. The grouped parameter is only honored if
64916      * the store has a groupField.
64917      * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
64918      */
64919     last: function(grouped) {
64920         var me = this;
64921
64922         if (grouped && me.isGrouped()) {
64923             return me.aggregate(function(records) {
64924                 var len = records.length;
64925                 return len ? records[len - 1] : undefined;
64926             }, me, true);
64927         } else {
64928             return me.data.last();
64929         }
64930     },
64931
64932     /**
64933      * Sums the value of <tt>property</tt> for each {@link Ext.data.Model record} between <tt>start</tt>
64934      * and <tt>end</tt> and returns the result.
64935      * @param {String} field A field in each record
64936      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64937      * in the store. The value returned will be an object literal with the key being the group
64938      * name and the sum for that group being the value. The grouped parameter is only honored if
64939      * the store has a groupField.
64940      * @return {Number} The sum
64941      */
64942     sum: function(field, grouped) {
64943         var me = this;
64944
64945         if (grouped && me.isGrouped()) {
64946             return me.aggregate(me.getSum, me, true, [field]);
64947         } else {
64948             return me.getSum(me.data.items, field);
64949         }
64950     },
64951
64952     // @private, see sum
64953     getSum: function(records, field) {
64954         var total = 0,
64955             i = 0,
64956             len = records.length;
64957
64958         for (; i < len; ++i) {
64959             total += records[i].get(field);
64960         }
64961
64962         return total;
64963     },
64964
64965     /**
64966      * Gets the count of items in the store.
64967      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64968      * in the store. The value returned will be an object literal with the key being the group
64969      * name and the count for each group being the value. The grouped parameter is only honored if
64970      * the store has a groupField.
64971      * @return {Number} the count
64972      */
64973     count: function(grouped) {
64974         var me = this;
64975
64976         if (grouped && me.isGrouped()) {
64977             return me.aggregate(function(records) {
64978                 return records.length;
64979             }, me, true);
64980         } else {
64981             return me.getCount();
64982         }
64983     },
64984
64985     /**
64986      * Gets the minimum value in the store.
64987      * @param {String} field The field in each record
64988      * @param {Boolean} grouped (Optional) True to perform the operation for each group
64989      * in the store. The value returned will be an object literal with the key being the group
64990      * name and the minimum in the group being the value. The grouped parameter is only honored if
64991      * the store has a groupField.
64992      * @return {Mixed/undefined} The minimum value, if no items exist, undefined.
64993      */
64994     min: function(field, grouped) {
64995         var me = this;
64996
64997         if (grouped && me.isGrouped()) {
64998             return me.aggregate(me.getMin, me, true, [field]);
64999         } else {
65000             return me.getMin(me.data.items, field);
65001         }
65002     },
65003
65004     // @private, see min
65005     getMin: function(records, field){
65006         var i = 1,
65007             len = records.length,
65008             value, min;
65009
65010         if (len > 0) {
65011             min = records[0].get(field);
65012         }
65013
65014         for (; i < len; ++i) {
65015             value = records[i].get(field);
65016             if (value < min) {
65017                 min = value;
65018             }
65019         }
65020         return min;
65021     },
65022
65023     /**
65024      * Gets the maximum value in the store.
65025      * @param {String} field The field in each record
65026      * @param {Boolean} grouped (Optional) True to perform the operation for each group
65027      * in the store. The value returned will be an object literal with the key being the group
65028      * name and the maximum in the group being the value. The grouped parameter is only honored if
65029      * the store has a groupField.
65030      * @return {Mixed/undefined} The maximum value, if no items exist, undefined.
65031      */
65032     max: function(field, grouped) {
65033         var me = this;
65034
65035         if (grouped && me.isGrouped()) {
65036             return me.aggregate(me.getMax, me, true, [field]);
65037         } else {
65038             return me.getMax(me.data.items, field);
65039         }
65040     },
65041
65042     // @private, see max
65043     getMax: function(records, field) {
65044         var i = 1,
65045             len = records.length,
65046             value,
65047             max;
65048
65049         if (len > 0) {
65050             max = records[0].get(field);
65051         }
65052
65053         for (; i < len; ++i) {
65054             value = records[i].get(field);
65055             if (value > max) {
65056                 max = value;
65057             }
65058         }
65059         return max;
65060     },
65061
65062     /**
65063      * Gets the average value in the store.
65064      * @param {String} field The field in each record
65065      * @param {Boolean} grouped (Optional) True to perform the operation for each group
65066      * in the store. The value returned will be an object literal with the key being the group
65067      * name and the group average being the value. The grouped parameter is only honored if
65068      * the store has a groupField.
65069      * @return {Mixed/undefined} The average value, if no items exist, 0.
65070      */
65071     average: function(field, grouped) {
65072         var me = this;
65073         if (grouped && me.isGrouped()) {
65074             return me.aggregate(me.getAverage, me, true, [field]);
65075         } else {
65076             return me.getAverage(me.data.items, field);
65077         }
65078     },
65079
65080     // @private, see average
65081     getAverage: function(records, field) {
65082         var i = 0,
65083             len = records.length,
65084             sum = 0;
65085
65086         if (records.length > 0) {
65087             for (; i < len; ++i) {
65088                 sum += records[i].get(field);
65089             }
65090             return sum / len;
65091         }
65092         return 0;
65093     },
65094
65095     /**
65096      * Runs the aggregate function for all the records in the store.
65097      * @param {Function} fn The function to execute. The function is called with a single parameter,
65098      * an array of records for that group.
65099      * @param {Object} scope (optional) The scope to execute the function in. Defaults to the store.
65100      * @param {Boolean} grouped (Optional) True to perform the operation for each group
65101      * in the store. The value returned will be an object literal with the key being the group
65102      * name and the group average being the value. The grouped parameter is only honored if
65103      * the store has a groupField.
65104      * @param {Array} args (optional) Any arguments to append to the function call
65105      * @return {Object} An object literal with the group names and their appropriate values.
65106      */
65107     aggregate: function(fn, scope, grouped, args) {
65108         args = args || [];
65109         if (grouped && this.isGrouped()) {
65110             var groups = this.getGroups(),
65111                 i = 0,
65112                 len = groups.length,
65113                 out = {},
65114                 group;
65115
65116             for (; i < len; ++i) {
65117                 group = groups[i];
65118                 out[group.name] = fn.apply(scope || this, [group.children].concat(args));
65119             }
65120             return out;
65121         } else {
65122             return fn.apply(scope || this, [this.data.items].concat(args));
65123         }
65124     }
65125 });
65126
65127 /**
65128  * @author Ed Spencer
65129  * @class Ext.data.JsonStore
65130  * @extends Ext.data.Store
65131  * @ignore
65132  *
65133  * <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
65134  * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.</p>
65135  *
65136  * <p>A store configuration would be something like:</p>
65137  *
65138 <pre><code>
65139 var store = new Ext.data.JsonStore({
65140     // store configs
65141     autoDestroy: true,
65142     storeId: 'myStore'
65143
65144     proxy: {
65145         type: 'ajax',
65146         url: 'get-images.php',
65147         reader: {
65148             type: 'json',
65149             root: 'images',
65150             idProperty: 'name'
65151         }
65152     },
65153
65154     //alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
65155     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
65156 });
65157 </code></pre>
65158  *
65159  * <p>This store is configured to consume a returned object of the form:<pre><code>
65160 {
65161     images: [
65162         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
65163         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
65164     ]
65165 }
65166 </code></pre>
65167  *
65168  * <p>An object literal of this form could also be used as the {@link #data} config option.</p>
65169  *
65170  * @constructor
65171  * @param {Object} config
65172  * @xtype jsonstore
65173  */
65174 Ext.define('Ext.data.JsonStore',  {
65175     extend: 'Ext.data.Store',
65176     alias: 'store.json',
65177
65178     /**
65179      * @cfg {Ext.data.DataReader} reader @hide
65180      */
65181     constructor: function(config) {
65182         config = config || {};
65183
65184         Ext.applyIf(config, {
65185             proxy: {
65186                 type  : 'ajax',
65187                 reader: 'json',
65188                 writer: 'json'
65189             }
65190         });
65191
65192         this.callParent([config]);
65193     }
65194 });
65195
65196 /**
65197  * @class Ext.chart.axis.Time
65198  * @extends Ext.chart.axis.Axis
65199  *
65200  * A type of axis whose units are measured in time values. Use this axis
65201  * for listing dates that you will want to group or dynamically change.
65202  * If you just want to display dates as categories then use the
65203  * Category class for axis instead.
65204  *
65205  * For example:
65206  *
65207  *     axes: [{
65208  *         type: 'Time',
65209  *         position: 'bottom',
65210  *         fields: 'date',
65211  *         title: 'Day',
65212  *         dateFormat: 'M d',
65213  *         groupBy: 'year,month,day',
65214  *         aggregateOp: 'sum',
65215  *     
65216  *         constrain: true,
65217  *         fromDate: new Date('1/1/11'),
65218  *         toDate: new Date('1/7/11')
65219  *     }]
65220  *
65221  * In this example we're creating a time axis that has as title *Day*.
65222  * The field the axis is bound to is `date`.
65223  * The date format to use to display the text for the axis labels is `M d`
65224  * which is a three letter month abbreviation followed by the day number.
65225  * The time axis will show values for dates between `fromDate` and `toDate`.
65226  * Since `constrain` is set to true all other values for other dates not between
65227  * the fromDate and toDate will not be displayed.
65228  * 
65229  * @constructor
65230  */
65231 Ext.define('Ext.chart.axis.Time', {
65232
65233     /* Begin Definitions */
65234
65235     extend: 'Ext.chart.axis.Category',
65236
65237     alternateClassName: 'Ext.chart.TimeAxis',
65238
65239     alias: 'axis.time',
65240
65241     requires: ['Ext.data.Store', 'Ext.data.JsonStore'],
65242
65243     /* End Definitions */
65244
65245      /**
65246       * The minimum value drawn by the axis. If not set explicitly, the axis
65247       * minimum will be calculated automatically.
65248       * @property calculateByLabelSize
65249       * @type Boolean
65250       */
65251     calculateByLabelSize: true,
65252     
65253      /**
65254      * Indicates the format the date will be rendered on. 
65255      * For example: 'M d' will render the dates as 'Jan 30', etc.
65256       *
65257      * @property dateFormat
65258      * @type {String|Boolean}
65259       */
65260     dateFormat: false,
65261     
65262      /**
65263      * Indicates the time unit to use for each step. Can be 'day', 'month', 'year' or a comma-separated combination of all of them.
65264      * Default's 'year,month,day'.
65265      *
65266      * @property timeUnit
65267      * @type {String}
65268      */
65269     groupBy: 'year,month,day',
65270     
65271     /**
65272      * Aggregation operation when grouping. Possible options are 'sum', 'avg', 'max', 'min'. Default's 'sum'.
65273      * 
65274      * @property aggregateOp
65275      * @type {String}
65276       */
65277     aggregateOp: 'sum',
65278     
65279     /**
65280      * The starting date for the time axis.
65281      * @property fromDate
65282      * @type Date
65283      */
65284     fromDate: false,
65285     
65286     /**
65287      * The ending date for the time axis.
65288      * @property toDate
65289      * @type Date
65290      */
65291     toDate: false,
65292     
65293     /**
65294      * An array with two components: The first is the unit of the step (day, month, year, etc). The second one is the number of units for the step (1, 2, etc.).
65295      * Default's [Ext.Date.DAY, 1].
65296      * 
65297      * @property step 
65298      * @type Array
65299      */
65300     step: [Ext.Date.DAY, 1],
65301     
65302     /**
65303      * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate. 
65304      * If false, the time axis will adapt to the new values by adding/removing steps.
65305      * Default's [Ext.Date.DAY, 1].
65306      * 
65307      * @property constrain 
65308      * @type Boolean
65309      */
65310     constrain: false,
65311     
65312     // @private a wrapper for date methods.
65313     dateMethods: {
65314         'year': function(date) {
65315             return date.getFullYear();
65316         },
65317         'month': function(date) {
65318             return date.getMonth() + 1;
65319         },
65320         'day': function(date) {
65321             return date.getDate();
65322         },
65323         'hour': function(date) {
65324             return date.getHours();
65325         },
65326         'minute': function(date) {
65327             return date.getMinutes();
65328         },
65329         'second': function(date) {
65330             return date.getSeconds();
65331         },
65332         'millisecond': function(date) {
65333             return date.getMilliseconds();
65334         }
65335     },
65336     
65337     // @private holds aggregate functions.
65338     aggregateFn: (function() {
65339         var etype = (function() {
65340             var rgxp = /^\[object\s(.*)\]$/,
65341                 toString = Object.prototype.toString;
65342             return function(e) {
65343                 return toString.call(e).match(rgxp)[1];
65344             };
65345         })();
65346         return {
65347             'sum': function(list) {
65348                 var i = 0, l = list.length, acum = 0;
65349                 if (!list.length || etype(list[0]) != 'Number') {
65350                     return list[0];
65351                 }
65352                 for (; i < l; i++) {
65353                     acum += list[i];
65354                 }
65355                 return acum;
65356             },
65357             'max': function(list) {
65358                 if (!list.length || etype(list[0]) != 'Number') {
65359                     return list[0];
65360                 }
65361                 return Math.max.apply(Math, list);
65362             },
65363             'min': function(list) {
65364                 if (!list.length || etype(list[0]) != 'Number') {
65365                     return list[0];
65366                 }
65367                 return Math.min.apply(Math, list);
65368             },
65369             'avg': function(list) {
65370                 var i = 0, l = list.length, acum = 0;
65371                 if (!list.length || etype(list[0]) != 'Number') {
65372                     return list[0];
65373                 }
65374                 for (; i < l; i++) {
65375                     acum += list[i];
65376                 }
65377                 return acum / l;
65378             }
65379         };
65380     })(),
65381     
65382     // @private normalized the store to fill date gaps in the time interval.
65383     constrainDates: function() {
65384         var fromDate = Ext.Date.clone(this.fromDate),
65385             toDate = Ext.Date.clone(this.toDate),
65386             step = this.step,
65387             field = this.fields,
65388             store = this.chart.store,
65389             record, recObj, fieldNames = [],
65390             newStore = Ext.create('Ext.data.Store', {
65391                 model: store.model
65392             });
65393         
65394         var getRecordByDate = (function() {
65395             var index = 0, l = store.getCount();
65396             return function(date) {
65397                 var rec, recDate;
65398                 for (; index < l; index++) {
65399                     rec = store.getAt(index);
65400                     recDate = rec.get(field);
65401                     if (+recDate > +date) {
65402                         return false;
65403                     } else if (+recDate == +date) {
65404                         return rec;
65405                     }
65406                 }
65407                 return false;
65408             };
65409         })();
65410         
65411         if (!this.constrain) {
65412             this.chart.filteredStore = this.chart.store;
65413             return;
65414         }
65415
65416         while(+fromDate <= +toDate) {
65417             record = getRecordByDate(fromDate);
65418             recObj = {};
65419             if (record) {
65420                 newStore.add(record.data);
65421             } else {
65422                 newStore.model.prototype.fields.each(function(f) {
65423                     recObj[f.name] = false;
65424                 });
65425                 recObj.date = fromDate;
65426                 newStore.add(recObj);
65427             }
65428             fromDate = Ext.Date.add(fromDate, step[0], step[1]);
65429         }
65430          
65431         this.chart.filteredStore = newStore;
65432     },
65433     
65434     // @private aggregates values if multiple store elements belong to the same time step.
65435     aggregate: function() {
65436         var aggStore = {}, 
65437             aggKeys = [], key, value,
65438             op = this.aggregateOp,
65439             field = this.fields, i,
65440             fields = this.groupBy.split(','),
65441             curField,
65442             recFields = [],
65443             recFieldsLen = 0,
65444             obj,
65445             dates = [],
65446             json = [],
65447             l = fields.length,
65448             dateMethods = this.dateMethods,
65449             aggregateFn = this.aggregateFn,
65450             store = this.chart.filteredStore || this.chart.store;
65451         
65452         store.each(function(rec) {
65453             //get all record field names in a simple array
65454             if (!recFields.length) {
65455                 rec.fields.each(function(f) {
65456                     recFields.push(f.name);
65457                 });
65458                 recFieldsLen = recFields.length;
65459             }
65460             //get record date value
65461             value = rec.get(field);
65462             //generate key for grouping records
65463             for (i = 0; i < l; i++) {
65464                 if (i == 0) {
65465                     key = String(dateMethods[fields[i]](value));
65466                 } else {
65467                     key += '||' + dateMethods[fields[i]](value);
65468                 }
65469             }
65470             //get aggregation record from hash
65471             if (key in aggStore) {
65472                 obj = aggStore[key];
65473             } else {
65474                 obj = aggStore[key] = {};
65475                 aggKeys.push(key);
65476                 dates.push(value);
65477             }
65478             //append record values to an aggregation record
65479             for (i = 0; i < recFieldsLen; i++) {
65480                 curField = recFields[i];
65481                 if (!obj[curField]) {
65482                     obj[curField] = [];
65483                 }
65484                 if (rec.get(curField) !== undefined) {
65485                     obj[curField].push(rec.get(curField));
65486                 }
65487             }
65488         });
65489         //perform aggregation operations on fields
65490         for (key in aggStore) {
65491             obj = aggStore[key];
65492             for (i = 0; i < recFieldsLen; i++) {
65493                 curField = recFields[i];
65494                 obj[curField] = aggregateFn[op](obj[curField]);
65495             }
65496             json.push(obj);
65497         }
65498         this.chart.substore = Ext.create('Ext.data.JsonStore', {
65499             fields: recFields,
65500             data: json
65501         });
65502         
65503         this.dates = dates;
65504     },
65505     
65506     // @private creates a label array to be used as the axis labels.
65507      setLabels: function() {
65508         var store = this.chart.substore,
65509             fields = this.fields,
65510             format = this.dateFormat,
65511             labels, i, dates = this.dates,
65512             formatFn = Ext.Date.format;
65513         this.labels = labels = [];
65514         store.each(function(record, i) {
65515             if (!format) {
65516                 labels.push(record.get(fields));
65517             } else {
65518                 labels.push(formatFn(dates[i], format));
65519             }
65520          }, this);
65521      },
65522
65523     processView: function() {
65524          //TODO(nico): fix this eventually...
65525          if (this.constrain) {
65526              this.constrainDates();
65527              this.aggregate();
65528              this.chart.substore = this.chart.filteredStore;
65529          } else {
65530              this.aggregate();
65531          }
65532     },
65533
65534      // @private modifies the store and creates the labels for the axes.
65535      applyData: function() {
65536         this.setLabels();
65537         var count = this.chart.substore.getCount();
65538          return {
65539              from: 0,
65540              to: count,
65541              steps: count - 1,
65542              step: 1
65543          };
65544      }
65545  });
65546
65547
65548 /**
65549  * @class Ext.chart.series.Series
65550  * 
65551  * Series is the abstract class containing the common logic to all chart series. Series includes 
65552  * methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling 
65553  * mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
65554  *
65555  * ## Listeners
65556  *
65557  * The series class supports listeners via the Observable syntax. Some of these listeners are:
65558  *
65559  *  - `itemmouseup` When the user interacts with a marker.
65560  *  - `itemmousedown` When the user interacts with a marker.
65561  *  - `itemmousemove` When the user iteracts with a marker.
65562  *  - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
65563  *
65564  * For example:
65565  *
65566  *     series: [{
65567  *             type: 'column',
65568  *             axis: 'left',
65569  *             listeners: {
65570  *                     'afterrender': function() {
65571  *                             console('afterrender');
65572  *                     }
65573  *             },
65574  *             xField: 'category',
65575  *             yField: 'data1'
65576  *     }]
65577  *     
65578  */
65579 Ext.define('Ext.chart.series.Series', {
65580
65581     /* Begin Definitions */
65582
65583     mixins: {
65584         observable: 'Ext.util.Observable',
65585         labels: 'Ext.chart.Label',
65586         highlights: 'Ext.chart.Highlight',
65587         tips: 'Ext.chart.Tip',
65588         callouts: 'Ext.chart.Callout'
65589     },
65590
65591     /* End Definitions */
65592
65593     /**
65594      * @cfg {Boolean|Object} highlight
65595      * If set to `true` it will highlight the markers or the series when hovering
65596      * with the mouse. This parameter can also be an object with the same style
65597      * properties you would apply to a {@link Ext.draw.Sprite} to apply custom
65598      * styles to markers and series.
65599      */
65600
65601     /**
65602      * @cfg {Object} tips
65603      * Add tooltips to the visualization's markers. The options for the tips are the
65604      * same configuration used with {@link Ext.tip.ToolTip}. For example:
65605      *
65606      *     tips: {
65607      *       trackMouse: true,
65608      *       width: 140,
65609      *       height: 28,
65610      *       renderer: function(storeItem, item) {
65611      *         this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
65612      *       }
65613      *     },
65614      */
65615
65616     /**
65617      * @cfg {String} type
65618      * The type of series. Set in subclasses.
65619      */
65620     type: null,
65621
65622     /**
65623      * @cfg {String} title
65624      * The human-readable name of the series.
65625      */
65626     title: null,
65627
65628     /**
65629      * @cfg {Boolean} showInLegend
65630      * Whether to show this series in the legend.
65631      */
65632     showInLegend: true,
65633
65634     /**
65635      * @cfg {Function} renderer
65636      * A function that can be overridden to set custom styling properties to each rendered element.
65637      * Passes in (sprite, record, attributes, index, store) to the function.
65638      */
65639     renderer: function(sprite, record, attributes, index, store) {
65640         return attributes;
65641     },
65642
65643     /**
65644      * @cfg {Array} shadowAttributes
65645      * An array with shadow attributes
65646      */
65647     shadowAttributes: null,
65648     
65649     //@private triggerdrawlistener flag
65650     triggerAfterDraw: false,
65651
65652     /**
65653      * @cfg {Object} listeners  
65654      * An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
65655      *  
65656      *  <ul>
65657      *      <li>itemmouseover</li>
65658      *      <li>itemmouseout</li>
65659      *      <li>itemmousedown</li>
65660      *      <li>itemmouseup</li>
65661      *  </ul>
65662      */
65663     
65664     constructor: function(config) {
65665         var me = this;
65666         if (config) {
65667             Ext.apply(me, config);
65668         }
65669         
65670         me.shadowGroups = [];
65671         
65672         me.mixins.labels.constructor.call(me, config);
65673         me.mixins.highlights.constructor.call(me, config);
65674         me.mixins.tips.constructor.call(me, config);
65675         me.mixins.callouts.constructor.call(me, config);
65676
65677         me.addEvents({
65678             scope: me,
65679             itemmouseover: true,
65680             itemmouseout: true,
65681             itemmousedown: true,
65682             itemmouseup: true,
65683             mouseleave: true,
65684             afterdraw: true,
65685
65686             /**
65687              * @event titlechange
65688              * Fires when the series title is changed via {@link #setTitle}.
65689              * @param {String} title The new title value
65690              * @param {Number} index The index in the collection of titles
65691              */
65692             titlechange: true
65693         });
65694
65695         me.mixins.observable.constructor.call(me, config);
65696
65697         me.on({
65698             scope: me,
65699             itemmouseover: me.onItemMouseOver,
65700             itemmouseout: me.onItemMouseOut,
65701             mouseleave: me.onMouseLeave
65702         });
65703     },
65704
65705     // @private set the bbox and clipBox for the series
65706     setBBox: function(noGutter) {
65707         var me = this,
65708             chart = me.chart,
65709             chartBBox = chart.chartBBox,
65710             gutterX = noGutter ? 0 : chart.maxGutter[0],
65711             gutterY = noGutter ? 0 : chart.maxGutter[1],
65712             clipBox, bbox;
65713
65714         clipBox = {
65715             x: chartBBox.x,
65716             y: chartBBox.y,
65717             width: chartBBox.width,
65718             height: chartBBox.height
65719         };
65720         me.clipBox = clipBox;
65721
65722         bbox = {
65723             x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
65724             y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
65725             width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
65726             height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
65727         };
65728         me.bbox = bbox;
65729     },
65730
65731     // @private set the animation for the sprite
65732     onAnimate: function(sprite, attr) {
65733         var me = this;
65734         sprite.stopAnimation();
65735         if (me.triggerAfterDraw) {
65736             return sprite.animate(Ext.applyIf(attr, me.chart.animate));
65737         } else {
65738             me.triggerAfterDraw = true;
65739             return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
65740                 listeners: {
65741                     'afteranimate': function() {
65742                         me.triggerAfterDraw = false;
65743                         me.fireEvent('afterrender');
65744                     }    
65745                 }    
65746             }));
65747         }
65748     },
65749     
65750     // @private return the gutter.
65751     getGutters: function() {
65752         return [0, 0];
65753     },
65754
65755     // @private wrapper for the itemmouseover event.
65756     onItemMouseOver: function(item) { 
65757         var me = this;
65758         if (item.series === me) {
65759             if (me.highlight) {
65760                 me.highlightItem(item);
65761             }
65762             if (me.tooltip) {
65763                 me.showTip(item);
65764             }
65765         }
65766     },
65767
65768     // @private wrapper for the itemmouseout event.
65769     onItemMouseOut: function(item) {
65770         var me = this;
65771         if (item.series === me) {
65772             me.unHighlightItem();
65773             if (me.tooltip) {
65774                 me.hideTip(item);
65775             }
65776         }
65777     },
65778
65779     // @private wrapper for the mouseleave event.
65780     onMouseLeave: function() {
65781         var me = this;
65782         me.unHighlightItem();
65783         if (me.tooltip) {
65784             me.hideTip();
65785         }
65786     },
65787
65788     /**
65789      * For a given x/y point relative to the Surface, find a corresponding item from this
65790      * series, if any.
65791      * @param {Number} x
65792      * @param {Number} y
65793      * @return {Object} An object describing the item, or null if there is no matching item. The exact contents of
65794      *                  this object will vary by series type, but should always contain at least the following:
65795      *                  <ul>
65796      *                    <li>{Ext.chart.series.Series} series - the Series object to which the item belongs</li>
65797      *                    <li>{Object} value - the value(s) of the item's data point</li>
65798      *                    <li>{Array} point - the x/y coordinates relative to the chart box of a single point
65799      *                        for this data item, which can be used as e.g. a tooltip anchor point.</li>
65800      *                    <li>{Ext.draw.Sprite} sprite - the item's rendering Sprite.
65801      *                  </ul>
65802      */
65803     getItemForPoint: function(x, y) {
65804         //if there are no items to query just return null.
65805         if (!this.items || !this.items.length || this.seriesIsHidden) {
65806             return null;
65807         }
65808         var me = this,
65809             items = me.items,
65810             bbox = me.bbox,
65811             item, i, ln;
65812         // Check bounds
65813         if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
65814             return null;
65815         }
65816         for (i = 0, ln = items.length; i < ln; i++) {
65817             if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
65818                 return items[i];
65819             }
65820         }
65821         
65822         return null;
65823     },
65824     
65825     isItemInPoint: function(x, y, item, i) {
65826         return false;
65827     },
65828
65829     /**
65830      * Hides all the elements in the series.
65831      */
65832     hideAll: function() {
65833         var me = this,
65834             items = me.items,
65835             item, len, i, sprite;
65836
65837         me.seriesIsHidden = true;
65838         me._prevShowMarkers = me.showMarkers;
65839
65840         me.showMarkers = false;
65841         //hide all labels
65842         me.hideLabels(0);
65843         //hide all sprites
65844         for (i = 0, len = items.length; i < len; i++) {
65845             item = items[i];
65846             sprite = item.sprite;
65847             if (sprite) {
65848                 sprite.setAttributes({
65849                     hidden: true
65850                 }, true);
65851             }
65852         }
65853     },
65854
65855     /**
65856      * Shows all the elements in the series.
65857      */
65858     showAll: function() {
65859         var me = this,
65860             prevAnimate = me.chart.animate;
65861         me.chart.animate = false;
65862         me.seriesIsHidden = false;
65863         me.showMarkers = me._prevShowMarkers;
65864         me.drawSeries();
65865         me.chart.animate = prevAnimate;
65866     },
65867     
65868     /**
65869      * Returns a string with the color to be used for the series legend item. 
65870      */
65871     getLegendColor: function(index) {
65872         var me = this, fill, stroke;
65873         if (me.seriesStyle) {
65874             fill = me.seriesStyle.fill;
65875             stroke = me.seriesStyle.stroke;
65876             if (fill && fill != 'none') {
65877                 return fill;
65878             }
65879             return stroke;
65880         }
65881         return '#000';
65882     },
65883     
65884     /**
65885      * Checks whether the data field should be visible in the legend
65886      * @private
65887      * @param {Number} index The index of the current item
65888      */
65889     visibleInLegend: function(index){
65890         var excludes = this.__excludes;
65891         if (excludes) {
65892             return !excludes[index];
65893         }
65894         return !this.seriesIsHidden;
65895     },
65896
65897     /**
65898      * Changes the value of the {@link #title} for the series.
65899      * Arguments can take two forms:
65900      * <ul>
65901      * <li>A single String value: this will be used as the new single title for the series (applies
65902      * to series with only one yField)</li>
65903      * <li>A numeric index and a String value: this will set the title for a single indexed yField.</li>
65904      * </ul>
65905      * @param {Number} index
65906      * @param {String} title
65907      */
65908     setTitle: function(index, title) {
65909         var me = this,
65910             oldTitle = me.title;
65911
65912         if (Ext.isString(index)) {
65913             title = index;
65914             index = 0;
65915         }
65916
65917         if (Ext.isArray(oldTitle)) {
65918             oldTitle[index] = title;
65919         } else {
65920             me.title = title;
65921         }
65922
65923         me.fireEvent('titlechange', title, index);
65924     }
65925 });
65926
65927 /**
65928  * @class Ext.chart.series.Cartesian
65929  * @extends Ext.chart.series.Series
65930  *
65931  * Common base class for series implementations which plot values using x/y coordinates.
65932  *
65933  * @constructor
65934  */
65935 Ext.define('Ext.chart.series.Cartesian', {
65936
65937     /* Begin Definitions */
65938
65939     extend: 'Ext.chart.series.Series',
65940
65941     alternateClassName: ['Ext.chart.CartesianSeries', 'Ext.chart.CartesianChart'],
65942
65943     /* End Definitions */
65944
65945     /**
65946      * The field used to access the x axis value from the items from the data
65947      * source.
65948      *
65949      * @cfg xField
65950      * @type String
65951      */
65952     xField: null,
65953
65954     /**
65955      * The field used to access the y-axis value from the items from the data
65956      * source.
65957      *
65958      * @cfg yField
65959      * @type String
65960      */
65961     yField: null,
65962
65963     /**
65964      * Indicates which axis the series will bind to
65965      *
65966      * @property axis
65967      * @type String
65968      */
65969     axis: 'left'
65970 });
65971
65972 /**
65973  * @class Ext.chart.series.Area
65974  * @extends Ext.chart.series.Cartesian
65975  * 
65976  <p>
65977     Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
65978     As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart 
65979     documentation for more information. A typical configuration object for the area series could be:
65980  </p>
65981 {@img Ext.chart.series.Area/Ext.chart.series.Area.png Ext.chart.series.Area chart series} 
65982   <pre><code>
65983    var store = Ext.create('Ext.data.JsonStore', {
65984         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
65985         data: [
65986             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
65987             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
65988             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
65989             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
65990             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
65991         ]
65992     });
65993     
65994     Ext.create('Ext.chart.Chart', {
65995         renderTo: Ext.getBody(),
65996         width: 500,
65997         height: 300,
65998         store: store,
65999         axes: [{
66000             type: 'Numeric',
66001             grid: true,
66002             position: 'left',
66003             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
66004             title: 'Sample Values',
66005             grid: {
66006                 odd: {
66007                     opacity: 1,
66008                     fill: '#ddd',
66009                     stroke: '#bbb',
66010                     'stroke-width': 1
66011                 }
66012             },
66013             minimum: 0,
66014             adjustMinimumByMajorUnit: 0
66015         }, {
66016             type: 'Category',
66017             position: 'bottom',
66018             fields: ['name'],
66019             title: 'Sample Metrics',
66020             grid: true,
66021             label: {
66022                 rotate: {
66023                     degrees: 315
66024                 }
66025             }
66026         }],
66027         series: [{
66028             type: 'area',
66029             highlight: false,
66030             axis: 'left',
66031             xField: 'name',
66032             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
66033             style: {
66034                 opacity: 0.93
66035             }
66036         }]
66037     });
66038    </code></pre>
66039  
66040   
66041  <p>
66042   In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover, 
66043   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, 
66044   and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity 
66045   to the style object.
66046  </p>
66047   
66048  * @xtype area
66049  * 
66050  */
66051 Ext.define('Ext.chart.series.Area', {
66052
66053     /* Begin Definitions */
66054
66055     extend: 'Ext.chart.series.Cartesian',
66056     
66057     alias: 'series.area',
66058
66059     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
66060
66061     /* End Definitions */
66062
66063     type: 'area',
66064
66065     // @private Area charts are alyways stacked
66066     stacked: true,
66067
66068     /**
66069      * @cfg {Object} style 
66070      * Append styling properties to this object for it to override theme properties.
66071      */
66072     style: {},
66073
66074     constructor: function(config) {
66075         this.callParent(arguments);
66076         var me = this,
66077             surface = me.chart.surface,
66078             i, l;
66079         Ext.apply(me, config, {
66080             __excludes: [],
66081             highlightCfg: {
66082                 lineWidth: 3,
66083                 stroke: '#55c',
66084                 opacity: 0.8,
66085                 color: '#f00'
66086             }
66087         });
66088         if (me.highlight) {
66089             me.highlightSprite = surface.add({
66090                 type: 'path',
66091                 path: ['M', 0, 0],
66092                 zIndex: 1000,
66093                 opacity: 0.3,
66094                 lineWidth: 5,
66095                 hidden: true,
66096                 stroke: '#444'
66097             });
66098         }
66099         me.group = surface.getGroup(me.seriesId);
66100     },
66101
66102     // @private Shrinks dataSets down to a smaller size
66103     shrink: function(xValues, yValues, size) {
66104         var len = xValues.length,
66105             ratio = Math.floor(len / size),
66106             i, j,
66107             xSum = 0,
66108             yCompLen = this.areas.length,
66109             ySum = [],
66110             xRes = [],
66111             yRes = [];
66112         //initialize array
66113         for (j = 0; j < yCompLen; ++j) {
66114             ySum[j] = 0;
66115         }
66116         for (i = 0; i < len; ++i) {
66117             xSum += xValues[i];
66118             for (j = 0; j < yCompLen; ++j) {
66119                 ySum[j] += yValues[i][j];
66120             }
66121             if (i % ratio == 0) {
66122                 //push averages
66123                 xRes.push(xSum/ratio);
66124                 for (j = 0; j < yCompLen; ++j) {
66125                     ySum[j] /= ratio;
66126                 }
66127                 yRes.push(ySum);
66128                 //reset sum accumulators
66129                 xSum = 0;
66130                 for (j = 0, ySum = []; j < yCompLen; ++j) {
66131                     ySum[j] = 0;
66132                 }
66133             }
66134         }
66135         return {
66136             x: xRes,
66137             y: yRes
66138         };
66139     },
66140
66141     // @private Get chart and data boundaries
66142     getBounds: function() {
66143         var me = this,
66144             chart = me.chart,
66145             store = chart.substore || chart.store,
66146             areas = [].concat(me.yField),
66147             areasLen = areas.length,
66148             xValues = [],
66149             yValues = [],
66150             infinity = Infinity,
66151             minX = infinity,
66152             minY = infinity,
66153             maxX = -infinity,
66154             maxY = -infinity,
66155             math = Math,
66156             mmin = math.min,
66157             mmax = math.max,
66158             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
66159
66160         me.setBBox();
66161         bbox = me.bbox;
66162
66163         // Run through the axis
66164         if (me.axis) {
66165             axis = chart.axes.get(me.axis);
66166             if (axis) {
66167                 out = axis.calcEnds();
66168                 minY = out.from || axis.prevMin;
66169                 maxY = mmax(out.to || axis.prevMax, 0);
66170             }
66171         }
66172
66173         if (me.yField && !Ext.isNumber(minY)) {
66174             axis = Ext.create('Ext.chart.axis.Axis', {
66175                 chart: chart,
66176                 fields: [].concat(me.yField)
66177             });
66178             out = axis.calcEnds();
66179             minY = out.from || axis.prevMin;
66180             maxY = mmax(out.to || axis.prevMax, 0);
66181         }
66182
66183         if (!Ext.isNumber(minY)) {
66184             minY = 0;
66185         }
66186         if (!Ext.isNumber(maxY)) {
66187             maxY = 0;
66188         }
66189
66190         store.each(function(record, i) {
66191             xValue = record.get(me.xField);
66192             yValue = [];
66193             if (typeof xValue != 'number') {
66194                 xValue = i;
66195             }
66196             xValues.push(xValue);
66197             acumY = 0;
66198             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
66199                 areaElem = record.get(areas[areaIndex]);
66200                 if (typeof areaElem == 'number') {
66201                     minY = mmin(minY, areaElem);
66202                     yValue.push(areaElem);
66203                     acumY += areaElem;
66204                 }
66205             }
66206             minX = mmin(minX, xValue);
66207             maxX = mmax(maxX, xValue);
66208             maxY = mmax(maxY, acumY);
66209             yValues.push(yValue);
66210         }, me);
66211
66212         xScale = bbox.width / (maxX - minX);
66213         yScale = bbox.height / (maxY - minY);
66214
66215         ln = xValues.length;
66216         if ((ln > bbox.width) && me.areas) {
66217             sumValues = me.shrink(xValues, yValues, bbox.width);
66218             xValues = sumValues.x;
66219             yValues = sumValues.y;
66220         }
66221
66222         return {
66223             bbox: bbox,
66224             minX: minX,
66225             minY: minY,
66226             xValues: xValues,
66227             yValues: yValues,
66228             xScale: xScale,
66229             yScale: yScale,
66230             areasLen: areasLen
66231         };
66232     },
66233
66234     // @private Build an array of paths for the chart
66235     getPaths: function() {
66236         var me = this,
66237             chart = me.chart,
66238             store = chart.substore || chart.store,
66239             first = true,
66240             bounds = me.getBounds(),
66241             bbox = bounds.bbox,
66242             items = me.items = [],
66243             componentPaths = [],
66244             componentPath,
66245             paths = [],
66246             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
66247
66248         ln = bounds.xValues.length;
66249         // Start the path
66250         for (i = 0; i < ln; i++) {
66251             xValue = bounds.xValues[i];
66252             yValue = bounds.yValues[i];
66253             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
66254             acumY = 0;
66255             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
66256                 // Excluded series
66257                 if (me.__excludes[areaIndex]) {
66258                     continue;
66259                 }
66260                 if (!componentPaths[areaIndex]) {
66261                     componentPaths[areaIndex] = [];
66262                 }
66263                 areaElem = yValue[areaIndex];
66264                 acumY += areaElem;
66265                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
66266                 if (!paths[areaIndex]) {
66267                     paths[areaIndex] = ['M', x, y];
66268                     componentPaths[areaIndex].push(['L', x, y]);
66269                 } else {
66270                     paths[areaIndex].push('L', x, y);
66271                     componentPaths[areaIndex].push(['L', x, y]);
66272                 }
66273                 if (!items[areaIndex]) {
66274                     items[areaIndex] = {
66275                         pointsUp: [],
66276                         pointsDown: [],
66277                         series: me
66278                     };
66279                 }
66280                 items[areaIndex].pointsUp.push([x, y]);
66281             }
66282         }
66283         
66284         // Close the paths
66285         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
66286             // Excluded series
66287             if (me.__excludes[areaIndex]) {
66288                 continue;
66289             }
66290             path = paths[areaIndex];
66291             // Close bottom path to the axis
66292             if (areaIndex == 0 || first) {
66293                 first = false;
66294                 path.push('L', x, bbox.y + bbox.height,
66295                           'L', bbox.x, bbox.y + bbox.height,
66296                           'Z');
66297             }
66298             // Close other paths to the one before them
66299             else {
66300                 componentPath = componentPaths[prevAreaIndex];
66301                 componentPath.reverse();
66302                 path.push('L', x, componentPath[0][2]);
66303                 for (i = 0; i < ln; i++) {
66304                     path.push(componentPath[i][0],
66305                               componentPath[i][1],
66306                               componentPath[i][2]);
66307                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
66308                 }
66309                 path.push('L', bbox.x, path[2], 'Z');
66310             }
66311             prevAreaIndex = areaIndex;
66312         }
66313         return {
66314             paths: paths,
66315             areasLen: bounds.areasLen
66316         };
66317     },
66318
66319     /**
66320      * Draws the series for the current chart.
66321      */
66322     drawSeries: function() {
66323         var me = this,
66324             chart = me.chart,
66325             store = chart.substore || chart.store,
66326             surface = chart.surface,
66327             animate = chart.animate,
66328             group = me.group,
66329             endLineStyle = Ext.apply(me.seriesStyle, me.style),
66330             colorArrayStyle = me.colorArrayStyle,
66331             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
66332             areaIndex, areaElem, paths, path, rendererAttributes;
66333
66334         me.unHighlightItem();
66335         me.cleanHighlights();
66336
66337         if (!store || !store.getCount()) {
66338             return;
66339         }
66340         
66341         paths = me.getPaths();
66342
66343         if (!me.areas) {
66344             me.areas = [];
66345         }
66346
66347         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
66348             // Excluded series
66349             if (me.__excludes[areaIndex]) {
66350                 continue;
66351             }
66352             if (!me.areas[areaIndex]) {
66353                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
66354                     type: 'path',
66355                     group: group,
66356                     // 'clip-rect': me.clipBox,
66357                     path: paths.paths[areaIndex],
66358                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
66359                     fill: colorArrayStyle[areaIndex % colorArrayLength]
66360                 }, endLineStyle || {}));
66361             }
66362             areaElem = me.areas[areaIndex];
66363             path = paths.paths[areaIndex];
66364             if (animate) {
66365                 //Add renderer to line. There is not a unique record associated with this.
66366                 rendererAttributes = me.renderer(areaElem, false, { 
66367                     path: path,
66368                     // 'clip-rect': me.clipBox,
66369                     fill: colorArrayStyle[areaIndex % colorArrayLength],
66370                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
66371                 }, areaIndex, store);
66372                 //fill should not be used here but when drawing the special fill path object
66373                 me.animation = me.onAnimate(areaElem, {
66374                     to: rendererAttributes
66375                 });
66376             } else {
66377                 rendererAttributes = me.renderer(areaElem, false, { 
66378                     path: path,
66379                     // 'clip-rect': me.clipBox,
66380                     hidden: false,
66381                     fill: colorArrayStyle[areaIndex % colorArrayLength],
66382                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
66383                 }, areaIndex, store);
66384                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
66385             }
66386         }
66387         me.renderLabels();
66388         me.renderCallouts();
66389     },
66390
66391     // @private
66392     onAnimate: function(sprite, attr) {
66393         sprite.show();
66394         return this.callParent(arguments);
66395     },
66396
66397     // @private
66398     onCreateLabel: function(storeItem, item, i, display) {
66399         var me = this,
66400             group = me.labelsGroup,
66401             config = me.label,
66402             bbox = me.bbox,
66403             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
66404
66405         return me.chart.surface.add(Ext.apply({
66406             'type': 'text',
66407             'text-anchor': 'middle',
66408             'group': group,
66409             'x': item.point[0],
66410             'y': bbox.y + bbox.height / 2
66411         }, endLabelStyle || {}));
66412     },
66413
66414     // @private
66415     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
66416         var me = this,
66417             chart = me.chart,
66418             resizing = chart.resizing,
66419             config = me.label,
66420             format = config.renderer,
66421             field = config.field,
66422             bbox = me.bbox,
66423             x = item.point[0],
66424             y = item.point[1],
66425             bb, width, height;
66426         
66427         label.setAttributes({
66428             text: format(storeItem.get(field[index])),
66429             hidden: true
66430         }, true);
66431         
66432         bb = label.getBBox();
66433         width = bb.width / 2;
66434         height = bb.height / 2;
66435         
66436         x = x - width < bbox.x? bbox.x + width : x;
66437         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
66438         y = y - height < bbox.y? bbox.y + height : y;
66439         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
66440
66441         if (me.chart.animate && !me.chart.resizing) {
66442             label.show(true);
66443             me.onAnimate(label, {
66444                 to: {
66445                     x: x,
66446                     y: y
66447                 }
66448             });
66449         } else {
66450             label.setAttributes({
66451                 x: x,
66452                 y: y
66453             }, true);
66454             if (resizing) {
66455                 me.animation.on('afteranimate', function() {
66456                     label.show(true);
66457                 });
66458             } else {
66459                 label.show(true);
66460             }
66461         }
66462     },
66463
66464     // @private
66465     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
66466         var me = this,
66467             chart = me.chart,
66468             surface = chart.surface,
66469             resizing = chart.resizing,
66470             config = me.callouts,
66471             items = me.items,
66472             prev = (i == 0) ? false : items[i -1].point,
66473             next = (i == items.length -1) ? false : items[i +1].point,
66474             cur = item.point,
66475             dir, norm, normal, a, aprev, anext,
66476             bbox = callout.label.getBBox(),
66477             offsetFromViz = 30,
66478             offsetToSide = 10,
66479             offsetBox = 3,
66480             boxx, boxy, boxw, boxh,
66481             p, clipRect = me.clipRect,
66482             x, y;
66483
66484         //get the right two points
66485         if (!prev) {
66486             prev = cur;
66487         }
66488         if (!next) {
66489             next = cur;
66490         }
66491         a = (next[1] - prev[1]) / (next[0] - prev[0]);
66492         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
66493         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
66494         
66495         norm = Math.sqrt(1 + a * a);
66496         dir = [1 / norm, a / norm];
66497         normal = [-dir[1], dir[0]];
66498         
66499         //keep the label always on the outer part of the "elbow"
66500         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
66501             normal[0] *= -1;
66502             normal[1] *= -1;
66503         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
66504             normal[0] *= -1;
66505             normal[1] *= -1;
66506         }
66507
66508         //position
66509         x = cur[0] + normal[0] * offsetFromViz;
66510         y = cur[1] + normal[1] * offsetFromViz;
66511         
66512         //box position and dimensions
66513         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
66514         boxy = y - bbox.height /2 - offsetBox;
66515         boxw = bbox.width + 2 * offsetBox;
66516         boxh = bbox.height + 2 * offsetBox;
66517         
66518         //now check if we're out of bounds and invert the normal vector correspondingly
66519         //this may add new overlaps between labels (but labels won't be out of bounds).
66520         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
66521             normal[0] *= -1;
66522         }
66523         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
66524             normal[1] *= -1;
66525         }
66526
66527         //update positions
66528         x = cur[0] + normal[0] * offsetFromViz;
66529         y = cur[1] + normal[1] * offsetFromViz;
66530         
66531         //update box position and dimensions
66532         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
66533         boxy = y - bbox.height /2 - offsetBox;
66534         boxw = bbox.width + 2 * offsetBox;
66535         boxh = bbox.height + 2 * offsetBox;
66536         
66537         //set the line from the middle of the pie to the box.
66538         callout.lines.setAttributes({
66539             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
66540         }, true);
66541         //set box position
66542         callout.box.setAttributes({
66543             x: boxx,
66544             y: boxy,
66545             width: boxw,
66546             height: boxh
66547         }, true);
66548         //set text position
66549         callout.label.setAttributes({
66550             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
66551             y: y
66552         }, true);
66553         for (p in callout) {
66554             callout[p].show(true);
66555         }
66556     },
66557     
66558     isItemInPoint: function(x, y, item, i) {
66559         var me = this,
66560             pointsUp = item.pointsUp,
66561             pointsDown = item.pointsDown,
66562             abs = Math.abs,
66563             dist = Infinity, p, pln, point;
66564         
66565         for (p = 0, pln = pointsUp.length; p < pln; p++) {
66566             point = [pointsUp[p][0], pointsUp[p][1]];
66567             if (dist > abs(x - point[0])) {
66568                 dist = abs(x - point[0]);
66569             } else {
66570                 point = pointsUp[p -1];
66571                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
66572                     item.storeIndex = p -1;
66573                     item.storeField = me.yField[i];
66574                     item.storeItem = me.chart.store.getAt(p -1);
66575                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
66576                     return true;
66577                 } else {
66578                     break;
66579                 }
66580             }
66581         }
66582         return false;
66583     },
66584
66585     /**
66586      * Highlight this entire series.
66587      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
66588      */
66589     highlightSeries: function() {
66590         var area, to, fillColor;
66591         if (this._index !== undefined) {
66592             area = this.areas[this._index];
66593             if (area.__highlightAnim) {
66594                 area.__highlightAnim.paused = true;
66595             }
66596             area.__highlighted = true;
66597             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
66598             area.__prevFill = area.__prevFill || area.attr.fill;
66599             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
66600             fillColor = Ext.draw.Color.fromString(area.__prevFill);
66601             to = {
66602                 lineWidth: (area.__prevLineWidth || 0) + 2
66603             };
66604             if (fillColor) {
66605                 to.fill = fillColor.getLighter(0.2).toString();
66606             }
66607             else {
66608                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
66609             }
66610             if (this.chart.animate) {
66611                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
66612                     target: area,
66613                     to: to
66614                 }, this.chart.animate));
66615             }
66616             else {
66617                 area.setAttributes(to, true);
66618             }
66619         }
66620     },
66621
66622     /**
66623      * UnHighlight this entire series.
66624      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
66625      */
66626     unHighlightSeries: function() {
66627         var area;
66628         if (this._index !== undefined) {
66629             area = this.areas[this._index];
66630             if (area.__highlightAnim) {
66631                 area.__highlightAnim.paused = true;
66632             }
66633             if (area.__highlighted) {
66634                 area.__highlighted = false;
66635                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
66636                     target: area,
66637                     to: {
66638                         fill: area.__prevFill,
66639                         opacity: area.__prevOpacity,
66640                         lineWidth: area.__prevLineWidth
66641                     }
66642                 });
66643             }
66644         }
66645     },
66646
66647     /**
66648      * Highlight the specified item. If no item is provided the whole series will be highlighted.
66649      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
66650      */
66651     highlightItem: function(item) {
66652         var me = this,
66653             points, path;
66654         if (!item) {
66655             this.highlightSeries();
66656             return;
66657         }
66658         points = item._points;
66659         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
66660                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
66661         me.highlightSprite.setAttributes({
66662             path: path,
66663             hidden: false
66664         }, true);
66665     },
66666
66667     /**
66668      * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
66669      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
66670      */
66671     unHighlightItem: function(item) {
66672         if (!item) {
66673             this.unHighlightSeries();
66674         }
66675
66676         if (this.highlightSprite) {
66677             this.highlightSprite.hide(true);
66678         }
66679     },
66680
66681     // @private
66682     hideAll: function() {
66683         if (!isNaN(this._index)) {
66684             this.__excludes[this._index] = true;
66685             this.areas[this._index].hide(true);
66686             this.drawSeries();
66687         }
66688     },
66689
66690     // @private
66691     showAll: function() {
66692         if (!isNaN(this._index)) {
66693             this.__excludes[this._index] = false;
66694             this.areas[this._index].show(true);
66695             this.drawSeries();
66696         }
66697     },
66698
66699     /**
66700      * Returns the color of the series (to be displayed as color for the series legend item).
66701      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
66702      */
66703     getLegendColor: function(index) {
66704         var me = this;
66705         return me.colorArrayStyle[index % me.colorArrayStyle.length];
66706     }
66707 });
66708
66709 /**
66710  * Creates a Bar Chart. A Bar Chart is a useful visualization technique to display quantitative information for
66711  * different categories that can show some progression (or regression) in the dataset. As with all other series, the Bar
66712  * Series must be appended in the *series* Chart array configuration. See the Chart documentation for more information.
66713  * A typical configuration object for the bar series could be:
66714  *
66715  * {@img Ext.chart.series.Bar/Ext.chart.series.Bar.png Ext.chart.series.Bar chart series}
66716  *
66717  *     var store = Ext.create('Ext.data.JsonStore', {
66718  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
66719  *         data: [
66720  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
66721  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
66722  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
66723  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
66724  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
66725  *         ]
66726  *     });
66727  *     
66728  *     Ext.create('Ext.chart.Chart', {
66729  *         renderTo: Ext.getBody(),
66730  *         width: 500,
66731  *         height: 300,
66732  *         animate: true,
66733  *         store: store,
66734  *         axes: [{
66735  *             type: 'Numeric',
66736  *             position: 'bottom',
66737  *             fields: ['data1'],
66738  *             label: {
66739  *                 renderer: Ext.util.Format.numberRenderer('0,0')
66740  *             },
66741  *             title: 'Sample Values',
66742  *             grid: true,
66743  *             minimum: 0
66744  *         }, {
66745  *             type: 'Category',
66746  *             position: 'left',
66747  *             fields: ['name'],
66748  *             title: 'Sample Metrics'
66749  *         }],
66750  *         series: [{
66751  *             type: 'bar',
66752  *             axis: 'bottom',
66753  *             highlight: true,
66754  *             tips: {
66755  *               trackMouse: true,
66756  *               width: 140,
66757  *               height: 28,
66758  *               renderer: function(storeItem, item) {
66759  *                 this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
66760  *               }
66761  *             },
66762  *             label: {
66763  *               display: 'insideEnd',
66764  *                 field: 'data1',
66765  *                 renderer: Ext.util.Format.numberRenderer('0'),
66766  *                 orientation: 'horizontal',
66767  *                 color: '#333',
66768  *                 'text-anchor': 'middle'
66769  *             },
66770  *             xField: 'name',
66771  *             yField: ['data1']
66772  *         }]
66773  *     });
66774  *
66775  * In this configuration we set `bar` as the series type, bind the values of the bar to the bottom axis and set the
66776  * xField or category field to the `name` parameter of the store. We also set `highlight` to true which enables smooth
66777  * animations when bars are hovered. We also set some configuration for the bar labels to be displayed inside the bar,
66778  * to display the information found in the `data1` property of each element store, to render a formated text with the
66779  * `Ext.util.Format` we pass in, to have an `horizontal` orientation (as opposed to a vertical one) and we also set
66780  * other styles like `color`, `text-anchor`, etc.
66781  */
66782 Ext.define('Ext.chart.series.Bar', {
66783
66784     /* Begin Definitions */
66785
66786     extend: 'Ext.chart.series.Cartesian',
66787
66788     alternateClassName: ['Ext.chart.BarSeries', 'Ext.chart.BarChart', 'Ext.chart.StackedBarChart'],
66789
66790     requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
66791
66792     /* End Definitions */
66793
66794     type: 'bar',
66795
66796     alias: 'series.bar',
66797     /**
66798      * @cfg {Boolean} column Whether to set the visualization as column chart or horizontal bar chart.
66799      */
66800     column: false,
66801     
66802     /**
66803      * @cfg style Style properties that will override the theming series styles.
66804      */
66805     style: {},
66806     
66807     /**
66808      * @cfg {Number} gutter The gutter space between single bars, as a percentage of the bar width
66809      */
66810     gutter: 38.2,
66811
66812     /**
66813      * @cfg {Number} groupGutter The gutter space between groups of bars, as a percentage of the bar width
66814      */
66815     groupGutter: 38.2,
66816
66817     /**
66818      * @cfg {Number} xPadding Padding between the left/right axes and the bars
66819      */
66820     xPadding: 0,
66821
66822     /**
66823      * @cfg {Number} yPadding Padding between the top/bottom axes and the bars
66824      */
66825     yPadding: 10,
66826
66827     constructor: function(config) {
66828         this.callParent(arguments);
66829         var me = this,
66830             surface = me.chart.surface,
66831             shadow = me.chart.shadow,
66832             i, l;
66833         Ext.apply(me, config, {
66834             highlightCfg: {
66835                 lineWidth: 3,
66836                 stroke: '#55c',
66837                 opacity: 0.8,
66838                 color: '#f00'
66839             },
66840             
66841             shadowAttributes: [{
66842                 "stroke-width": 6,
66843                 "stroke-opacity": 0.05,
66844                 stroke: 'rgb(200, 200, 200)',
66845                 translate: {
66846                     x: 1.2,
66847                     y: 1.2
66848                 }
66849             }, {
66850                 "stroke-width": 4,
66851                 "stroke-opacity": 0.1,
66852                 stroke: 'rgb(150, 150, 150)',
66853                 translate: {
66854                     x: 0.9,
66855                     y: 0.9
66856                 }
66857             }, {
66858                 "stroke-width": 2,
66859                 "stroke-opacity": 0.15,
66860                 stroke: 'rgb(100, 100, 100)',
66861                 translate: {
66862                     x: 0.6,
66863                     y: 0.6
66864                 }
66865             }]
66866         });
66867         me.group = surface.getGroup(me.seriesId + '-bars');
66868         if (shadow) {
66869             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
66870                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
66871             }
66872         }
66873     },
66874
66875     // @private sets the bar girth.
66876     getBarGirth: function() {
66877         var me = this,
66878             store = me.chart.store,
66879             column = me.column,
66880             ln = store.getCount(),
66881             gutter = me.gutter / 100;
66882         
66883         return (me.chart.chartBBox[column ? 'width' : 'height'] - me[column ? 'xPadding' : 'yPadding'] * 2) / (ln * (gutter + 1) - gutter);
66884     },
66885
66886     // @private returns the gutters.
66887     getGutters: function() {
66888         var me = this,
66889             column = me.column,
66890             gutter = Math.ceil(me[column ? 'xPadding' : 'yPadding'] + me.getBarGirth() / 2);
66891         return me.column ? [gutter, 0] : [0, gutter];
66892     },
66893
66894     // @private Get chart and data boundaries
66895     getBounds: function() {
66896         var me = this,
66897             chart = me.chart,
66898             store = chart.substore || chart.store,
66899             bars = [].concat(me.yField),
66900             barsLen = bars.length,
66901             groupBarsLen = barsLen,
66902             groupGutter = me.groupGutter / 100,
66903             column = me.column,
66904             xPadding = me.xPadding,
66905             yPadding = me.yPadding,
66906             stacked = me.stacked,
66907             barWidth = me.getBarGirth(),
66908             math = Math,
66909             mmax = math.max,
66910             mabs = math.abs,
66911             groupBarWidth, bbox, minY, maxY, axis, out,
66912             scale, zero, total, rec, j, plus, minus;
66913
66914         me.setBBox(true);
66915         bbox = me.bbox;
66916
66917         //Skip excluded series
66918         if (me.__excludes) {
66919             for (j = 0, total = me.__excludes.length; j < total; j++) {
66920                 if (me.__excludes[j]) {
66921                     groupBarsLen--;
66922                 }
66923             }
66924         }
66925
66926         if (me.axis) {
66927             axis = chart.axes.get(me.axis);
66928             if (axis) {
66929                 out = axis.calcEnds();
66930                 minY = out.from || axis.prevMin;
66931                 maxY = mmax(out.to || axis.prevMax, 0);
66932             }
66933         }
66934
66935         if (me.yField && !Ext.isNumber(minY)) {
66936             axis = Ext.create('Ext.chart.axis.Axis', {
66937                 chart: chart,
66938                 fields: [].concat(me.yField)
66939             });
66940             out = axis.calcEnds();
66941             minY = out.from || axis.prevMin;
66942             maxY = mmax(out.to || axis.prevMax, 0);
66943         }
66944
66945         if (!Ext.isNumber(minY)) {
66946             minY = 0;
66947         }
66948         if (!Ext.isNumber(maxY)) {
66949             maxY = 0;
66950         }
66951         scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (maxY - minY);
66952         groupBarWidth = barWidth / ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter);
66953         zero = (column) ? bbox.y + bbox.height - yPadding : bbox.x + xPadding;
66954
66955         if (stacked) {
66956             total = [[], []];
66957             store.each(function(record, i) {
66958                 total[0][i] = total[0][i] || 0;
66959                 total[1][i] = total[1][i] || 0;
66960                 for (j = 0; j < barsLen; j++) {
66961                     if (me.__excludes && me.__excludes[j]) {
66962                         continue;
66963                     }
66964                     rec = record.get(bars[j]);
66965                     total[+(rec > 0)][i] += mabs(rec);
66966                 }
66967             });
66968             total[+(maxY > 0)].push(mabs(maxY));
66969             total[+(minY > 0)].push(mabs(minY));
66970             minus = mmax.apply(math, total[0]);
66971             plus = mmax.apply(math, total[1]);
66972             scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (plus + minus);
66973             zero = zero + minus * scale * (column ? -1 : 1);
66974         }
66975         else if (minY / maxY < 0) {
66976             zero = zero - minY * scale * (column ? -1 : 1);
66977         }
66978         return {
66979             bars: bars,
66980             bbox: bbox,
66981             barsLen: barsLen,
66982             groupBarsLen: groupBarsLen,
66983             barWidth: barWidth,
66984             groupBarWidth: groupBarWidth,
66985             scale: scale,
66986             zero: zero,
66987             xPadding: xPadding,
66988             yPadding: yPadding,
66989             signed: minY / maxY < 0,
66990             minY: minY,
66991             maxY: maxY
66992         };
66993     },
66994
66995     // @private Build an array of paths for the chart
66996     getPaths: function() {
66997         var me = this,
66998             chart = me.chart,
66999             store = chart.substore || chart.store,
67000             bounds = me.bounds = me.getBounds(),
67001             items = me.items = [],
67002             gutter = me.gutter / 100,
67003             groupGutter = me.groupGutter / 100,
67004             animate = chart.animate,
67005             column = me.column,
67006             group = me.group,
67007             enableShadows = chart.shadow,
67008             shadowGroups = me.shadowGroups,
67009             shadowAttributes = me.shadowAttributes,
67010             shadowGroupsLn = shadowGroups.length,
67011             bbox = bounds.bbox,
67012             xPadding = me.xPadding,
67013             yPadding = me.yPadding,
67014             stacked = me.stacked,
67015             barsLen = bounds.barsLen,
67016             colors = me.colorArrayStyle,
67017             colorLength = colors && colors.length || 0,
67018             math = Math,
67019             mmax = math.max,
67020             mmin = math.min,
67021             mabs = math.abs,
67022             j, yValue, height, totalDim, totalNegDim, bottom, top, hasShadow, barAttr, attrs, counter,
67023             shadowIndex, shadow, sprite, offset, floorY;
67024
67025         store.each(function(record, i, total) {
67026             bottom = bounds.zero;
67027             top = bounds.zero;
67028             totalDim = 0;
67029             totalNegDim = 0;
67030             hasShadow = false; 
67031             for (j = 0, counter = 0; j < barsLen; j++) {
67032                 // Excluded series
67033                 if (me.__excludes && me.__excludes[j]) {
67034                     continue;
67035                 }
67036                 yValue = record.get(bounds.bars[j]);
67037                 height = Math.round((yValue - ((bounds.minY < 0) ? 0 : bounds.minY)) * bounds.scale);
67038                 barAttr = {
67039                     fill: colors[(barsLen > 1 ? j : 0) % colorLength]
67040                 };
67041                 if (column) {
67042                     Ext.apply(barAttr, {
67043                         height: height,
67044                         width: mmax(bounds.groupBarWidth, 0),
67045                         x: (bbox.x + xPadding + i * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked),
67046                         y: bottom - height
67047                     });
67048                 }
67049                 else {
67050                     // draw in reverse order
67051                     offset = (total - 1) - i;
67052                     Ext.apply(barAttr, {
67053                         height: mmax(bounds.groupBarWidth, 0),
67054                         width: height + (bottom == bounds.zero),
67055                         x: bottom + (bottom != bounds.zero),
67056                         y: (bbox.y + yPadding + offset * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked + 1)
67057                     });
67058                 }
67059                 if (height < 0) {
67060                     if (column) {
67061                         barAttr.y = top;
67062                         barAttr.height = mabs(height);
67063                     } else {
67064                         barAttr.x = top + height;
67065                         barAttr.width = mabs(height);
67066                     }
67067                 }
67068                 if (stacked) {
67069                     if (height < 0) {
67070                         top += height * (column ? -1 : 1);
67071                     } else {
67072                         bottom += height * (column ? -1 : 1);
67073                     }
67074                     totalDim += mabs(height);
67075                     if (height < 0) {
67076                         totalNegDim += mabs(height);
67077                     }
67078                 }
67079                 barAttr.x = Math.floor(barAttr.x) + 1;
67080                 floorY = Math.floor(barAttr.y);
67081                 if (!Ext.isIE9 && barAttr.y > floorY) {
67082                     floorY--;
67083                 }
67084                 barAttr.y = floorY;
67085                 barAttr.width = Math.floor(barAttr.width);
67086                 barAttr.height = Math.floor(barAttr.height);
67087                 items.push({
67088                     series: me,
67089                     storeItem: record,
67090                     value: [record.get(me.xField), yValue],
67091                     attr: barAttr,
67092                     point: column ? [barAttr.x + barAttr.width / 2, yValue >= 0 ? barAttr.y : barAttr.y + barAttr.height] :
67093                                     [yValue >= 0 ? barAttr.x + barAttr.width : barAttr.x, barAttr.y + barAttr.height / 2]
67094                 });
67095                 // When resizing, reset before animating
67096                 if (animate && chart.resizing) {
67097                     attrs = column ? {
67098                         x: barAttr.x,
67099                         y: bounds.zero,
67100                         width: barAttr.width,
67101                         height: 0
67102                     } : {
67103                         x: bounds.zero,
67104                         y: barAttr.y,
67105                         width: 0,
67106                         height: barAttr.height
67107                     };
67108                     if (enableShadows && (stacked && !hasShadow || !stacked)) {
67109                         hasShadow = true;
67110                         //update shadows
67111                         for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
67112                             shadow = shadowGroups[shadowIndex].getAt(stacked ? i : (i * barsLen + j));
67113                             if (shadow) {
67114                                 shadow.setAttributes(attrs, true);
67115                             }
67116                         }
67117                     }
67118                     //update sprite position and width/height
67119                     sprite = group.getAt(i * barsLen + j);
67120                     if (sprite) {
67121                         sprite.setAttributes(attrs, true);
67122                     }
67123                 }
67124                 counter++;
67125             }
67126             if (stacked && items.length) {
67127                 items[i * counter].totalDim = totalDim;
67128                 items[i * counter].totalNegDim = totalNegDim;
67129             }
67130         }, me);
67131     },
67132
67133     // @private render/setAttributes on the shadows
67134     renderShadows: function(i, barAttr, baseAttrs, bounds) {
67135         var me = this,
67136             chart = me.chart,
67137             surface = chart.surface,
67138             animate = chart.animate,
67139             stacked = me.stacked,
67140             shadowGroups = me.shadowGroups,
67141             shadowAttributes = me.shadowAttributes,
67142             shadowGroupsLn = shadowGroups.length,
67143             store = chart.substore || chart.store,
67144             column = me.column,
67145             items = me.items,
67146             shadows = [],
67147             zero = bounds.zero,
67148             shadowIndex, shadowBarAttr, shadow, totalDim, totalNegDim, j, rendererAttributes;
67149
67150         if ((stacked && (i % bounds.groupBarsLen === 0)) || !stacked) {
67151             j = i / bounds.groupBarsLen;
67152             //create shadows
67153             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
67154                 shadowBarAttr = Ext.apply({}, shadowAttributes[shadowIndex]);
67155                 shadow = shadowGroups[shadowIndex].getAt(stacked ? j : i);
67156                 Ext.copyTo(shadowBarAttr, barAttr, 'x,y,width,height');
67157                 if (!shadow) {
67158                     shadow = surface.add(Ext.apply({
67159                         type: 'rect',
67160                         group: shadowGroups[shadowIndex]
67161                     }, Ext.apply({}, baseAttrs, shadowBarAttr)));
67162                 }
67163                 if (stacked) {
67164                     totalDim = items[i].totalDim;
67165                     totalNegDim = items[i].totalNegDim;
67166                     if (column) {
67167                         shadowBarAttr.y = zero - totalNegDim;
67168                         shadowBarAttr.height = totalDim;
67169                     }
67170                     else {
67171                         shadowBarAttr.x = zero - totalNegDim;
67172                         shadowBarAttr.width = totalDim;
67173                     }
67174                 }
67175                 if (animate) {
67176                     if (!stacked) {
67177                         rendererAttributes = me.renderer(shadow, store.getAt(j), shadowBarAttr, i, store);
67178                         me.onAnimate(shadow, { to: rendererAttributes });
67179                     }
67180                     else {
67181                         rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: true }), i, store);
67182                         shadow.setAttributes(rendererAttributes, true);
67183                     }
67184                 }
67185                 else {
67186                     rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: false }), i, store);
67187                     shadow.setAttributes(rendererAttributes, true);
67188                 }
67189                 shadows.push(shadow);
67190             }
67191         }
67192         return shadows;
67193     },
67194
67195     /**
67196      * Draws the series for the current chart.
67197      */
67198     drawSeries: function() {
67199         var me = this,
67200             chart = me.chart,
67201             store = chart.substore || chart.store,
67202             surface = chart.surface,
67203             animate = chart.animate,
67204             stacked = me.stacked,
67205             column = me.column,
67206             enableShadows = chart.shadow,
67207             shadowGroups = me.shadowGroups,
67208             shadowGroupsLn = shadowGroups.length,
67209             group = me.group,
67210             seriesStyle = me.seriesStyle,
67211             items, ln, i, j, baseAttrs, sprite, rendererAttributes, shadowIndex, shadowGroup,
67212             bounds, endSeriesStyle, barAttr, attrs, anim;
67213         
67214         if (!store || !store.getCount()) {
67215             return;
67216         }
67217         
67218         //fill colors are taken from the colors array.
67219         delete seriesStyle.fill;
67220         endSeriesStyle = Ext.apply(seriesStyle, this.style);
67221         me.unHighlightItem();
67222         me.cleanHighlights();
67223
67224         me.getPaths();
67225         bounds = me.bounds;
67226         items = me.items;
67227
67228         baseAttrs = column ? {
67229             y: bounds.zero,
67230             height: 0
67231         } : {
67232             x: bounds.zero,
67233             width: 0
67234         };
67235         ln = items.length;
67236         // Create new or reuse sprites and animate/display
67237         for (i = 0; i < ln; i++) {
67238             sprite = group.getAt(i);
67239             barAttr = items[i].attr;
67240
67241             if (enableShadows) {
67242                 items[i].shadows = me.renderShadows(i, barAttr, baseAttrs, bounds);
67243             }
67244
67245             // Create a new sprite if needed (no height)
67246             if (!sprite) {
67247                 attrs = Ext.apply({}, baseAttrs, barAttr);
67248                 attrs = Ext.apply(attrs, endSeriesStyle || {});
67249                 sprite = surface.add(Ext.apply({}, {
67250                     type: 'rect',
67251                     group: group
67252                 }, attrs));
67253             }
67254             if (animate) {
67255                 rendererAttributes = me.renderer(sprite, store.getAt(i), barAttr, i, store);
67256                 sprite._to = rendererAttributes;
67257                 anim = me.onAnimate(sprite, { to: Ext.apply(rendererAttributes, endSeriesStyle) });
67258                 if (enableShadows && stacked && (i % bounds.barsLen === 0)) {
67259                     j = i / bounds.barsLen;
67260                     for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
67261                         anim.on('afteranimate', function() {
67262                             this.show(true);
67263                         }, shadowGroups[shadowIndex].getAt(j));
67264                     }
67265                 }
67266             }
67267             else {
67268                 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(barAttr, { hidden: false }), i, store);
67269                 sprite.setAttributes(Ext.apply(rendererAttributes, endSeriesStyle), true);
67270             }
67271             items[i].sprite = sprite;
67272         }
67273
67274         // Hide unused sprites
67275         ln = group.getCount();
67276         for (j = i; j < ln; j++) {
67277             group.getAt(j).hide(true);
67278         }
67279         // Hide unused shadows
67280         if (enableShadows) {
67281             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
67282                 shadowGroup = shadowGroups[shadowIndex];
67283                 ln = shadowGroup.getCount();
67284                 for (j = i; j < ln; j++) {
67285                     shadowGroup.getAt(j).hide(true);
67286                 }
67287             }
67288         }
67289         me.renderLabels();
67290     },
67291     
67292     // @private handled when creating a label.
67293     onCreateLabel: function(storeItem, item, i, display) {
67294         var me = this,
67295             surface = me.chart.surface,
67296             group = me.labelsGroup,
67297             config = me.label,
67298             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle || {}),
67299             sprite;
67300         return surface.add(Ext.apply({
67301             type: 'text',
67302             group: group
67303         }, endLabelStyle || {}));
67304     },
67305     
67306     // @private callback used when placing a label.
67307     onPlaceLabel: function(label, storeItem, item, i, display, animate, j, index) {
67308         // Determine the label's final position. Starts with the configured preferred value but
67309         // may get flipped from inside to outside or vice-versa depending on space.
67310         var me = this,
67311             opt = me.bounds,
67312             groupBarWidth = opt.groupBarWidth,
67313             column = me.column,
67314             chart = me.chart,
67315             chartBBox = chart.chartBBox,
67316             resizing = chart.resizing,
67317             xValue = item.value[0],
67318             yValue = item.value[1],
67319             attr = item.attr,
67320             config = me.label,
67321             rotate = config.orientation == 'vertical',
67322             field = [].concat(config.field),
67323             format = config.renderer,
67324             text = format(storeItem.get(field[index])),
67325             size = me.getLabelSize(text),
67326             width = size.width,
67327             height = size.height,
67328             zero = opt.zero,
67329             outside = 'outside',
67330             insideStart = 'insideStart',
67331             insideEnd = 'insideEnd',
67332             offsetX = 10,
67333             offsetY = 6,
67334             signed = opt.signed,
67335             x, y, finalAttr;
67336
67337         label.setAttributes({
67338             text: text
67339         });
67340
67341         if (column) {
67342             if (display == outside) {
67343                 if (height + offsetY + attr.height > (yValue >= 0 ? zero - chartBBox.y : chartBBox.y + chartBBox.height - zero)) {
67344                     display = insideEnd;
67345                 }
67346             } else {
67347                 if (height + offsetY > attr.height) {
67348                     display = outside;
67349                 }
67350             }
67351             x = attr.x + groupBarWidth / 2;
67352             y = display == insideStart ?
67353                     (zero + ((height / 2 + 3) * (yValue >= 0 ? -1 : 1))) :
67354                     (yValue >= 0 ? (attr.y + ((height / 2 + 3) * (display == outside ? -1 : 1))) :
67355                                    (attr.y + attr.height + ((height / 2 + 3) * (display === outside ? 1 : -1))));
67356         }
67357         else {
67358             if (display == outside) {
67359                 if (width + offsetX + attr.width > (yValue >= 0 ? chartBBox.x + chartBBox.width - zero : zero - chartBBox.x)) {
67360                     display = insideEnd;
67361                 }
67362             }
67363             else {
67364                 if (width + offsetX > attr.width) {
67365                     display = outside;
67366                 }
67367             }
67368             x = display == insideStart ?
67369                 (zero + ((width / 2 + 5) * (yValue >= 0 ? 1 : -1))) :
67370                 (yValue >= 0 ? (attr.x + attr.width + ((width / 2 + 5) * (display === outside ? 1 : -1))) :
67371                 (attr.x + ((width / 2 + 5) * (display === outside ? -1 : 1))));
67372             y = attr.y + groupBarWidth / 2;
67373         }
67374         //set position
67375         finalAttr = {
67376             x: x,
67377             y: y
67378         };
67379         //rotate
67380         if (rotate) {
67381             finalAttr.rotate = {
67382                 x: x,
67383                 y: y,
67384                 degrees: 270
67385             };
67386         }
67387         //check for resizing
67388         if (animate && resizing) {
67389             if (column) {
67390                 x = attr.x + attr.width / 2;
67391                 y = zero;
67392             } else {
67393                 x = zero;
67394                 y = attr.y + attr.height / 2;
67395             }
67396             label.setAttributes({
67397                 x: x,
67398                 y: y
67399             }, true);
67400             if (rotate) {
67401                 label.setAttributes({
67402                     rotate: {
67403                         x: x,
67404                         y: y,
67405                         degrees: 270
67406                     }
67407                 }, true);
67408             }
67409         }
67410         //handle animation
67411         if (animate) {
67412             me.onAnimate(label, { to: finalAttr });
67413         }
67414         else {
67415             label.setAttributes(Ext.apply(finalAttr, {
67416                 hidden: false
67417             }), true);
67418         }
67419     },
67420
67421     /* @private
67422      * Gets the dimensions of a given bar label. Uses a single hidden sprite to avoid
67423      * changing visible sprites.
67424      * @param value
67425      */
67426     getLabelSize: function(value) {
67427         var tester = this.testerLabel,
67428             config = this.label,
67429             endLabelStyle = Ext.apply({}, config, this.seriesLabelStyle || {}),
67430             rotated = config.orientation === 'vertical',
67431             bbox, w, h,
67432             undef;
67433         if (!tester) {
67434             tester = this.testerLabel = this.chart.surface.add(Ext.apply({
67435                 type: 'text',
67436                 opacity: 0
67437             }, endLabelStyle));
67438         }
67439         tester.setAttributes({
67440             text: value
67441         }, true);
67442
67443         // Flip the width/height if rotated, as getBBox returns the pre-rotated dimensions
67444         bbox = tester.getBBox();
67445         w = bbox.width;
67446         h = bbox.height;
67447         return {
67448             width: rotated ? h : w,
67449             height: rotated ? w : h
67450         };
67451     },
67452
67453     // @private used to animate label, markers and other sprites.
67454     onAnimate: function(sprite, attr) {
67455         sprite.show();
67456         return this.callParent(arguments);
67457     },
67458     
67459     isItemInPoint: function(x, y, item) {
67460         var bbox = item.sprite.getBBox();
67461         return bbox.x <= x && bbox.y <= y
67462             && (bbox.x + bbox.width) >= x
67463             && (bbox.y + bbox.height) >= y;
67464     },
67465     
67466     // @private hide all markers
67467     hideAll: function() {
67468         var axes = this.chart.axes;
67469         if (!isNaN(this._index)) {
67470             if (!this.__excludes) {
67471                 this.__excludes = [];
67472             }
67473             this.__excludes[this._index] = true;
67474             this.drawSeries();
67475             axes.each(function(axis) {
67476                 axis.drawAxis();
67477             });
67478         }
67479     },
67480
67481     // @private show all markers
67482     showAll: function() {
67483         var axes = this.chart.axes;
67484         if (!isNaN(this._index)) {
67485             if (!this.__excludes) {
67486                 this.__excludes = [];
67487             }
67488             this.__excludes[this._index] = false;
67489             this.drawSeries();
67490             axes.each(function(axis) {
67491                 axis.drawAxis();
67492             });
67493         }
67494     },
67495     
67496     /**
67497      * Returns a string with the color to be used for the series legend item.
67498      * @param index
67499      */
67500     getLegendColor: function(index) {
67501         var me = this;
67502         return me.colorArrayStyle[index % me.colorArrayStyle.length];
67503     }
67504 });
67505 /**
67506  * @class Ext.chart.series.Column
67507  * @extends Ext.chart.series.Bar
67508  * 
67509  * Creates a Column Chart. Much of the methods are inherited from Bar. A Column Chart is a useful visualization technique to display quantitative information for different 
67510  * categories that can show some progression (or regression) in the data set.
67511  * As with all other series, the Column Series must be appended in the *series* Chart array configuration. See the Chart 
67512  * documentation for more information. A typical configuration object for the column series could be:
67513  *
67514  * {@img Ext.chart.series.Column/Ext.chart.series.Column.png Ext.chart.series.Column chart series}
67515  *
67516  * ## Example
67517  * 
67518  *     var store = Ext.create('Ext.data.JsonStore', {
67519  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
67520  *         data: [
67521  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
67522  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
67523  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
67524  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
67525  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
67526  *         ]
67527  *     });
67528  *     
67529  *     Ext.create('Ext.chart.Chart', {
67530  *         renderTo: Ext.getBody(),
67531  *         width: 500,
67532  *         height: 300,
67533  *         animate: true,
67534  *         store: store,
67535  *         axes: [{
67536  *             type: 'Numeric',
67537  *             position: 'bottom',
67538  *             fields: ['data1'],
67539  *             label: {
67540  *                 renderer: Ext.util.Format.numberRenderer('0,0')
67541  *             },
67542  *             title: 'Sample Values',
67543  *             grid: true,
67544  *             minimum: 0
67545  *         }, {
67546  *             type: 'Category',
67547  *             position: 'left',
67548  *             fields: ['name'],
67549  *             title: 'Sample Metrics'
67550  *         }],
67551  *             axes: [{
67552  *                 type: 'Numeric',
67553  *                 position: 'left',
67554  *                 fields: ['data1'],
67555  *                 label: {
67556  *                     renderer: Ext.util.Format.numberRenderer('0,0')
67557  *                 },
67558  *                 title: 'Sample Values',
67559  *                 grid: true,
67560  *                 minimum: 0
67561  *             }, {
67562  *                 type: 'Category',
67563  *                 position: 'bottom',
67564  *                 fields: ['name'],
67565  *                 title: 'Sample Metrics'
67566  *             }],
67567  *             series: [{
67568  *                 type: 'column',
67569  *                 axis: 'left',
67570  *                 highlight: true,
67571  *                 tips: {
67572  *                   trackMouse: true,
67573  *                   width: 140,
67574  *                   height: 28,
67575  *                   renderer: function(storeItem, item) {
67576  *                     this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' $');
67577  *                   }
67578  *                 },
67579  *                 label: {
67580  *                   display: 'insideEnd',
67581  *                   'text-anchor': 'middle',
67582  *                     field: 'data1',
67583  *                     renderer: Ext.util.Format.numberRenderer('0'),
67584  *                     orientation: 'vertical',
67585  *                     color: '#333'
67586  *                 },
67587  *                 xField: 'name',
67588  *                 yField: 'data1'
67589  *             }]
67590  *     });
67591  *  
67592  * In this configuration we set `column` as the series type, bind the values of the bars to the bottom axis, set `highlight` to true so that bars are smoothly highlighted
67593  * when hovered and bind the `xField` or category field to the data store `name` property and the `yField` as the data1 property of a store element. 
67594  */
67595 Ext.define('Ext.chart.series.Column', {
67596
67597     /* Begin Definitions */
67598
67599     alternateClassName: ['Ext.chart.ColumnSeries', 'Ext.chart.ColumnChart', 'Ext.chart.StackedColumnChart'],
67600
67601     extend: 'Ext.chart.series.Bar',
67602
67603     /* End Definitions */
67604
67605     type: 'column',
67606     alias: 'series.column',
67607
67608     column: true,
67609
67610     /**
67611      * @cfg {Number} xPadding
67612      * Padding between the left/right axes and the bars
67613      */
67614     xPadding: 10,
67615
67616     /**
67617      * @cfg {Number} yPadding
67618      * Padding between the top/bottom axes and the bars
67619      */
67620     yPadding: 0
67621 });
67622 /**
67623  * @class Ext.chart.series.Gauge
67624  * @extends Ext.chart.series.Series
67625  * 
67626  * Creates a Gauge Chart. Gauge Charts are used to show progress in a certain variable. There are two ways of using the Gauge chart.
67627  * One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instanciating the
67628  * visualization and using the `setValue` method to adjust the value you want.
67629  *
67630  * A chart/series configuration for the Gauge visualization could look like this:
67631  * 
67632  *     {
67633  *         xtype: 'chart',
67634  *         store: store,
67635  *         axes: [{
67636  *             type: 'gauge',
67637  *             position: 'gauge',
67638  *             minimum: 0,
67639  *             maximum: 100,
67640  *             steps: 10,
67641  *             margin: -10
67642  *         }],
67643  *         series: [{
67644  *             type: 'gauge',
67645  *             field: 'data1',
67646  *             donut: false,
67647  *             colorSet: ['#F49D10', '#ddd']
67648  *         }]
67649  *     }
67650  * 
67651  * In this configuration we create a special Gauge axis to be used with the gauge visualization (describing half-circle markers), and also we're
67652  * setting a maximum, minimum and steps configuration options into the axis. The Gauge series configuration contains the store field to be bound to
67653  * the visual display and the color set to be used with the visualization.
67654  * 
67655  * @xtype gauge
67656  */
67657 Ext.define('Ext.chart.series.Gauge', {
67658
67659     /* Begin Definitions */
67660
67661     extend: 'Ext.chart.series.Series',
67662
67663     /* End Definitions */
67664
67665     type: "gauge",
67666     alias: 'series.gauge',
67667
67668     rad: Math.PI / 180,
67669
67670     /**
67671      * @cfg {Number} highlightDuration
67672      * The duration for the pie slice highlight effect.
67673      */
67674     highlightDuration: 150,
67675
67676     /**
67677      * @cfg {String} angleField
67678      * The store record field name to be used for the pie angles.
67679      * The values bound to this field name must be positive real numbers.
67680      * This parameter is required.
67681      */
67682     angleField: false,
67683
67684     /**
67685      * @cfg {Boolean} needle
67686      * Use the Gauge Series as an area series or add a needle to it. Default's false.
67687      */
67688     needle: false,
67689     
67690     /**
67691      * @cfg {Boolean|Number} donut
67692      * Use the entire disk or just a fraction of it for the gauge. Default's false.
67693      */
67694     donut: false,
67695
67696     /**
67697      * @cfg {Boolean} showInLegend
67698      * Whether to add the pie chart elements as legend items. Default's false.
67699      */
67700     showInLegend: false,
67701
67702     /**
67703      * @cfg {Object} style
67704      * An object containing styles for overriding series styles from Theming.
67705      */
67706     style: {},
67707     
67708     constructor: function(config) {
67709         this.callParent(arguments);
67710         var me = this,
67711             chart = me.chart,
67712             surface = chart.surface,
67713             store = chart.store,
67714             shadow = chart.shadow, i, l, cfg;
67715         Ext.apply(me, config, {
67716             shadowAttributes: [{
67717                 "stroke-width": 6,
67718                 "stroke-opacity": 1,
67719                 stroke: 'rgb(200, 200, 200)',
67720                 translate: {
67721                     x: 1.2,
67722                     y: 2
67723                 }
67724             },
67725             {
67726                 "stroke-width": 4,
67727                 "stroke-opacity": 1,
67728                 stroke: 'rgb(150, 150, 150)',
67729                 translate: {
67730                     x: 0.9,
67731                     y: 1.5
67732                 }
67733             },
67734             {
67735                 "stroke-width": 2,
67736                 "stroke-opacity": 1,
67737                 stroke: 'rgb(100, 100, 100)',
67738                 translate: {
67739                     x: 0.6,
67740                     y: 1
67741                 }
67742             }]
67743         });
67744         me.group = surface.getGroup(me.seriesId);
67745         if (shadow) {
67746             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
67747                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
67748             }
67749         }
67750         surface.customAttributes.segment = function(opt) {
67751             return me.getSegment(opt);
67752         };
67753     },
67754     
67755     //@private updates some onbefore render parameters.
67756     initialize: function() {
67757         var me = this,
67758             store = me.chart.substore || me.chart.store;
67759         //Add yFields to be used in Legend.js
67760         me.yField = [];
67761         if (me.label.field) {
67762             store.each(function(rec) {
67763                 me.yField.push(rec.get(me.label.field));
67764             });
67765         }
67766     },
67767
67768     // @private returns an object with properties for a Slice
67769     getSegment: function(opt) {
67770         var me = this,
67771             rad = me.rad,
67772             cos = Math.cos,
67773             sin = Math.sin,
67774             abs = Math.abs,
67775             x = me.centerX,
67776             y = me.centerY,
67777             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
67778             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
67779             delta = 1e-2,
67780             r = opt.endRho - opt.startRho,
67781             startAngle = opt.startAngle,
67782             endAngle = opt.endAngle,
67783             midAngle = (startAngle + endAngle) / 2 * rad,
67784             margin = opt.margin || 0,
67785             flag = abs(endAngle - startAngle) > 180,
67786             a1 = Math.min(startAngle, endAngle) * rad,
67787             a2 = Math.max(startAngle, endAngle) * rad,
67788             singleSlice = false;
67789
67790         x += margin * cos(midAngle);
67791         y += margin * sin(midAngle);
67792
67793         x1 = x + opt.startRho * cos(a1);
67794         y1 = y + opt.startRho * sin(a1);
67795
67796         x2 = x + opt.endRho * cos(a1);
67797         y2 = y + opt.endRho * sin(a1);
67798
67799         x3 = x + opt.startRho * cos(a2);
67800         y3 = y + opt.startRho * sin(a2);
67801
67802         x4 = x + opt.endRho * cos(a2);
67803         y4 = y + opt.endRho * sin(a2);
67804
67805         if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
67806             singleSlice = true;
67807         }
67808         //Solves mysterious clipping bug with IE
67809         if (singleSlice) {
67810             return {
67811                 path: [
67812                 ["M", x1, y1],
67813                 ["L", x2, y2],
67814                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
67815                 ["Z"]]
67816             };
67817         } else {
67818             return {
67819                 path: [
67820                 ["M", x1, y1],
67821                 ["L", x2, y2],
67822                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
67823                 ["L", x3, y3],
67824                 ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
67825                 ["Z"]]
67826             };
67827         }
67828     },
67829
67830     // @private utility function to calculate the middle point of a pie slice.
67831     calcMiddle: function(item) {
67832         var me = this,
67833             rad = me.rad,
67834             slice = item.slice,
67835             x = me.centerX,
67836             y = me.centerY,
67837             startAngle = slice.startAngle,
67838             endAngle = slice.endAngle,
67839             radius = Math.max(('rho' in slice) ? slice.rho: me.radius, me.label.minMargin),
67840             donut = +me.donut,
67841             a1 = Math.min(startAngle, endAngle) * rad,
67842             a2 = Math.max(startAngle, endAngle) * rad,
67843             midAngle = -(a1 + (a2 - a1) / 2),
67844             xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
67845             ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
67846
67847         item.middle = {
67848             x: xm,
67849             y: ym
67850         };
67851     },
67852
67853     /**
67854      * Draws the series for the current chart.
67855      */
67856     drawSeries: function() {
67857         var me = this,
67858             chart = me.chart,
67859             store = chart.substore || chart.store,
67860             group = me.group,
67861             animate = me.chart.animate,
67862             axis = me.chart.axes.get(0),
67863             minimum = axis && axis.minimum || me.minimum || 0,
67864             maximum = axis && axis.maximum || me.maximum || 0,
67865             field = me.angleField || me.field || me.xField,
67866             surface = chart.surface,
67867             chartBBox = chart.chartBBox,
67868             rad = me.rad,
67869             donut = +me.donut,
67870             values = {},
67871             items = [],
67872             seriesStyle = me.seriesStyle,
67873             seriesLabelStyle = me.seriesLabelStyle,
67874             colorArrayStyle = me.colorArrayStyle,
67875             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
67876             gutterX = chart.maxGutter[0],
67877             gutterY = chart.maxGutter[1],
67878             cos = Math.cos,
67879             sin = Math.sin,
67880             rendererAttributes, centerX, centerY, slice, slices, sprite, value,
67881             item, ln, record, i, j, startAngle, endAngle, middleAngle, sliceLength, path,
67882             p, spriteOptions, bbox, splitAngle, sliceA, sliceB;
67883         
67884         Ext.apply(seriesStyle, me.style || {});
67885
67886         me.setBBox();
67887         bbox = me.bbox;
67888
67889         //override theme colors
67890         if (me.colorSet) {
67891             colorArrayStyle = me.colorSet;
67892             colorArrayLength = colorArrayStyle.length;
67893         }
67894         
67895         //if not store or store is empty then there's nothing to draw
67896         if (!store || !store.getCount()) {
67897             return;
67898         }
67899         
67900         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
67901         centerY = me.centerY = chartBBox.y + chartBBox.height;
67902         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
67903         me.slices = slices = [];
67904         me.items = items = [];
67905         
67906         if (!me.value) {
67907             record = store.getAt(0);
67908             me.value = record.get(field);
67909         }
67910         
67911         value = me.value;
67912         if (me.needle) {
67913             sliceA = {
67914                 series: me,
67915                 value: value,
67916                 startAngle: -180,
67917                 endAngle: 0,
67918                 rho: me.radius
67919             };
67920             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
67921             slices.push(sliceA);
67922         } else {
67923             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
67924             sliceA = {
67925                 series: me,
67926                 value: value,
67927                 startAngle: -180,
67928                 endAngle: splitAngle,
67929                 rho: me.radius
67930             };
67931             sliceB = {
67932                 series: me,
67933                 value: me.maximum - value,
67934                 startAngle: splitAngle,
67935                 endAngle: 0,
67936                 rho: me.radius
67937             };
67938             slices.push(sliceA, sliceB);
67939         }
67940         
67941         //do pie slices after.
67942         for (i = 0, ln = slices.length; i < ln; i++) {
67943             slice = slices[i];
67944             sprite = group.getAt(i);
67945             //set pie slice properties
67946             rendererAttributes = Ext.apply({
67947                 segment: {
67948                     startAngle: slice.startAngle,
67949                     endAngle: slice.endAngle,
67950                     margin: 0,
67951                     rho: slice.rho,
67952                     startRho: slice.rho * +donut / 100,
67953                     endRho: slice.rho
67954                 } 
67955             }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
67956
67957             item = Ext.apply({},
67958             rendererAttributes.segment, {
67959                 slice: slice,
67960                 series: me,
67961                 storeItem: record,
67962                 index: i
67963             });
67964             items[i] = item;
67965             // Create a new sprite if needed (no height)
67966             if (!sprite) {
67967                 spriteOptions = Ext.apply({
67968                     type: "path",
67969                     group: group
67970                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
67971                 sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
67972             }
67973             slice.sprite = slice.sprite || [];
67974             item.sprite = sprite;
67975             slice.sprite.push(sprite);
67976             if (animate) {
67977                 rendererAttributes = me.renderer(sprite, record, rendererAttributes, i, store);
67978                 sprite._to = rendererAttributes;
67979                 me.onAnimate(sprite, {
67980                     to: rendererAttributes
67981                 });
67982             } else {
67983                 rendererAttributes = me.renderer(sprite, record, Ext.apply(rendererAttributes, {
67984                     hidden: false
67985                 }), i, store);
67986                 sprite.setAttributes(rendererAttributes, true);
67987             }
67988         }
67989         
67990         if (me.needle) {
67991             splitAngle = splitAngle * Math.PI / 180;
67992             
67993             if (!me.needleSprite) {
67994                 me.needleSprite = me.chart.surface.add({
67995                     type: 'path',
67996                     path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
67997                                 centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
67998                            'L', centerX + me.radius * cos(splitAngle),
67999                                 centerY + -Math.abs(me.radius * sin(splitAngle))],
68000                     'stroke-width': 4,
68001                     'stroke': '#222'
68002                 });
68003             } else {
68004                 if (animate) {
68005                     me.onAnimate(me.needleSprite, {
68006                         to: {
68007                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
68008                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
68009                                'L', centerX + me.radius * cos(splitAngle),
68010                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
68011                         }
68012                     });
68013                 } else {
68014                     me.needleSprite.setAttributes({
68015                         type: 'path',
68016                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
68017                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
68018                                'L', centerX + me.radius * cos(splitAngle),
68019                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
68020                     });
68021                 }
68022             }
68023             me.needleSprite.setAttributes({
68024                 hidden: false    
68025             }, true);
68026         }
68027         
68028         delete me.value;
68029     },
68030     
68031     /**
68032      * Sets the Gauge chart to the current specified value.
68033     */
68034     setValue: function (value) {
68035         this.value = value;
68036         this.drawSeries();
68037     },
68038
68039     // @private callback for when creating a label sprite.
68040     onCreateLabel: function(storeItem, item, i, display) {},
68041
68042     // @private callback for when placing a label sprite.
68043     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {},
68044
68045     // @private callback for when placing a callout.
68046     onPlaceCallout: function() {},
68047
68048     // @private handles sprite animation for the series.
68049     onAnimate: function(sprite, attr) {
68050         sprite.show();
68051         return this.callParent(arguments);
68052     },
68053
68054     isItemInPoint: function(x, y, item, i) {
68055         return false;
68056     },
68057     
68058     // @private shows all elements in the series.
68059     showAll: function() {
68060         if (!isNaN(this._index)) {
68061             this.__excludes[this._index] = false;
68062             this.drawSeries();
68063         }
68064     },
68065     
68066     /**
68067      * Returns the color of the series (to be displayed as color for the series legend item).
68068      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
68069      */
68070     getLegendColor: function(index) {
68071         var me = this;
68072         return me.colorArrayStyle[index % me.colorArrayStyle.length];
68073     }
68074 });
68075
68076
68077 /**
68078  * @class Ext.chart.series.Line
68079  * @extends Ext.chart.series.Cartesian
68080  * 
68081  * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different 
68082  * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
68083  * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart 
68084  * documentation for more information. A typical configuration object for the line series could be:
68085  *
68086  * {@img Ext.chart.series.Line/Ext.chart.series.Line.png Ext.chart.series.Line chart series}
68087  *
68088  *     var store = Ext.create('Ext.data.JsonStore', {
68089  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
68090  *         data: [
68091  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
68092  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
68093  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
68094  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
68095  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
68096  *         ]
68097  *     });
68098  *     
68099  *     Ext.create('Ext.chart.Chart', {
68100  *         renderTo: Ext.getBody(),
68101  *         width: 500,
68102  *         height: 300,
68103  *         animate: true,
68104  *         store: store,
68105  *         axes: [{
68106  *             type: 'Numeric',
68107  *             position: 'bottom',
68108  *             fields: ['data1'],
68109  *             label: {
68110  *                 renderer: Ext.util.Format.numberRenderer('0,0')
68111  *             },
68112  *             title: 'Sample Values',
68113  *             grid: true,
68114  *             minimum: 0
68115  *         }, {
68116  *             type: 'Category',
68117  *             position: 'left',
68118  *             fields: ['name'],
68119  *             title: 'Sample Metrics'
68120  *         }],
68121  *         series: [{
68122  *             type: 'line',
68123  *             highlight: {
68124  *                 size: 7,
68125  *                 radius: 7
68126  *             },
68127  *             axis: 'left',
68128  *             xField: 'name',
68129  *             yField: 'data1',
68130  *             markerCfg: {
68131  *                 type: 'cross',
68132  *                 size: 4,
68133  *                 radius: 4,
68134  *                 'stroke-width': 0
68135  *             }
68136  *         }, {
68137  *             type: 'line',
68138  *             highlight: {
68139  *                 size: 7,
68140  *                 radius: 7
68141  *             },
68142  *             axis: 'left',
68143  *             fill: true,
68144  *             xField: 'name',
68145  *             yField: 'data3',
68146  *             markerCfg: {
68147  *                 type: 'circle',
68148  *                 size: 4,
68149  *                 radius: 4,
68150  *                 'stroke-width': 0
68151  *             }
68152  *         }]
68153  *     });
68154  *  
68155  * In this configuration we're adding two series (or lines), one bound to the `data1` 
68156  * property of the store and the other to `data3`. The type for both configurations is 
68157  * `line`. The `xField` for both series is the same, the name propert of the store. 
68158  * Both line series share the same axis, the left axis. You can set particular marker 
68159  * configuration by adding properties onto the markerConfig object. Both series have 
68160  * an object as highlight so that markers animate smoothly to the properties in highlight 
68161  * when hovered. The second series has `fill=true` which means that the line will also 
68162  * have an area below it of the same color.
68163  *
68164  * **Note:** In the series definition remember to explicitly set the axis to bind the 
68165  * values of the line series to. This can be done by using the `axis` configuration property.
68166  */
68167 Ext.define('Ext.chart.series.Line', {
68168
68169     /* Begin Definitions */
68170
68171     extend: 'Ext.chart.series.Cartesian',
68172
68173     alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
68174
68175     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
68176
68177     /* End Definitions */
68178
68179     type: 'line',
68180     
68181     alias: 'series.line',
68182     
68183     /**
68184      * @cfg {String} axis
68185      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
68186      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
68187      * relative scale will be used.
68188      */
68189
68190     /**
68191      * @cfg {Number} selectionTolerance
68192      * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
68193      */
68194     selectionTolerance: 20,
68195     
68196     /**
68197      * @cfg {Boolean} showMarkers
68198      * Whether markers should be displayed at the data points along the line. If true,
68199      * then the {@link #markerConfig} config item will determine the markers' styling.
68200      */
68201     showMarkers: true,
68202
68203     /**
68204      * @cfg {Object} markerConfig
68205      * The display style for the markers. Only used if {@link #showMarkers} is true.
68206      * The markerConfig is a configuration object containing the same set of properties defined in
68207      * the Sprite class. For example, if we were to set red circles as markers to the line series we could
68208      * pass the object:
68209      *
68210      <pre><code>
68211         markerConfig: {
68212             type: 'circle',
68213             radius: 4,
68214             'fill': '#f00'
68215         }
68216      </code></pre>
68217      
68218      */
68219     markerConfig: {},
68220
68221     /**
68222      * @cfg {Object} style
68223      * An object containing styles for the visualization lines. These styles will override the theme styles. 
68224      * Some options contained within the style object will are described next.
68225      */
68226     style: {},
68227     
68228     /**
68229      * @cfg {Boolean} smooth
68230      * If true, the line will be smoothed/rounded around its points, otherwise straight line
68231      * segments will be drawn. Defaults to false.
68232      */
68233     smooth: false,
68234
68235     /**
68236      * @cfg {Boolean} fill
68237      * If true, the area below the line will be filled in using the {@link #style.eefill} and
68238      * {@link #style.opacity} config properties. Defaults to false.
68239      */
68240     fill: false,
68241
68242     constructor: function(config) {
68243         this.callParent(arguments);
68244         var me = this,
68245             surface = me.chart.surface,
68246             shadow = me.chart.shadow,
68247             i, l;
68248         Ext.apply(me, config, {
68249             highlightCfg: {
68250                 'stroke-width': 3
68251             },
68252             shadowAttributes: [{
68253                 "stroke-width": 6,
68254                 "stroke-opacity": 0.05,
68255                 stroke: 'rgb(0, 0, 0)',
68256                 translate: {
68257                     x: 1,
68258                     y: 1
68259                 }
68260             }, {
68261                 "stroke-width": 4,
68262                 "stroke-opacity": 0.1,
68263                 stroke: 'rgb(0, 0, 0)',
68264                 translate: {
68265                     x: 1,
68266                     y: 1
68267                 }
68268             }, {
68269                 "stroke-width": 2,
68270                 "stroke-opacity": 0.15,
68271                 stroke: 'rgb(0, 0, 0)',
68272                 translate: {
68273                     x: 1,
68274                     y: 1
68275                 }
68276             }]
68277         });
68278         me.group = surface.getGroup(me.seriesId);
68279         if (me.showMarkers) {
68280             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
68281         }
68282         if (shadow) {
68283             for (i = 0, l = this.shadowAttributes.length; i < l; i++) {
68284                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
68285             }
68286         }
68287     },
68288     
68289     // @private makes an average of points when there are more data points than pixels to be rendered.
68290     shrink: function(xValues, yValues, size) {
68291         // Start at the 2nd point...
68292         var len = xValues.length,
68293             ratio = Math.floor(len / size),
68294             i = 1,
68295             xSum = 0,
68296             ySum = 0,
68297             xRes = [xValues[0]],
68298             yRes = [yValues[0]];
68299         
68300         for (; i < len; ++i) {
68301             xSum += xValues[i] || 0;
68302             ySum += yValues[i] || 0;
68303             if (i % ratio == 0) {
68304                 xRes.push(xSum/ratio);
68305                 yRes.push(ySum/ratio);
68306                 xSum = 0;
68307                 ySum = 0;
68308             }
68309         }
68310         return {
68311             x: xRes,
68312             y: yRes
68313         };
68314     },
68315
68316     /**
68317      * Draws the series for the current chart.
68318      */
68319     drawSeries: function() {
68320         var me = this,
68321             chart = me.chart,
68322             store = chart.substore || chart.store,
68323             surface = chart.surface,
68324             chartBBox = chart.chartBBox,
68325             bbox = {},
68326             group = me.group,
68327             gutterX = chart.maxGutter[0],
68328             gutterY = chart.maxGutter[1],
68329             showMarkers = me.showMarkers,
68330             markerGroup = me.markerGroup,
68331             enableShadows = chart.shadow,
68332             shadowGroups = me.shadowGroups,
68333             shadowAttributes = this.shadowAttributes,
68334             lnsh = shadowGroups.length,
68335             dummyPath = ["M"],
68336             path = ["M"],
68337             markerIndex = chart.markerIndex,
68338             axes = [].concat(me.axis),
68339             shadowGroup,
68340             shadowBarAttr,
68341             xValues = [],
68342             yValues = [],
68343             numericAxis = true,
68344             axisCount = 0,
68345             onbreak = false,
68346             markerStyle = me.markerStyle,
68347             seriesStyle = me.seriesStyle,
68348             seriesLabelStyle = me.seriesLabelStyle,
68349             colorArrayStyle = me.colorArrayStyle,
68350             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
68351             posHash = {
68352                 'left': 'right',
68353                 'right': 'left',
68354                 'top': 'bottom',
68355                 'bottom': 'top'
68356             },
68357             seriesIdx = me.seriesIdx, shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
68358             x, y, prevX, prevY, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
68359             yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
68360             endLineStyle, type, props, firstMarker, count;
68361         
68362         //if store is empty then there's nothing to draw.
68363         if (!store || !store.getCount()) {
68364             return;
68365         }
68366         
68367         //prepare style objects for line and markers
68368         endMarkerStyle = Ext.apply(markerStyle, me.markerConfig);
68369         type = endMarkerStyle.type;
68370         delete endMarkerStyle.type;
68371         endLineStyle = Ext.apply(seriesStyle, me.style);
68372         //if no stroke with is specified force it to 0.5 because this is
68373         //about making *lines*
68374         if (!endLineStyle['stroke-width']) {
68375             endLineStyle['stroke-width'] = 0.5;
68376         }
68377         //If we're using a time axis and we need to translate the points,
68378         //then reuse the first markers as the last markers.
68379         if (markerIndex && markerGroup && markerGroup.getCount()) {
68380             for (i = 0; i < markerIndex; i++) {
68381                 marker = markerGroup.getAt(i);
68382                 markerGroup.remove(marker);
68383                 markerGroup.add(marker);
68384                 markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
68385                 marker.setAttributes({
68386                     x: 0,
68387                     y: 0,
68388                     translate: {
68389                         x: markerAux.attr.translation.x,
68390                         y: markerAux.attr.translation.y
68391                     }
68392                 }, true);
68393             }
68394         }
68395         
68396         me.unHighlightItem();
68397         me.cleanHighlights();
68398
68399         me.setBBox();
68400         bbox = me.bbox;
68401
68402         me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
68403
68404         chart.axes.each(function(axis) {
68405             //only apply position calculations to axes that affect this series
68406             //this means the axis in the position referred by this series and also
68407             //the axis in the other coordinate for this series. For example: (left, top|bottom),
68408             //or (top, left|right), etc.
68409             if (axis.position == me.axis || axis.position != posHash[me.axis]) {
68410                 axisCount++;
68411                 if (axis.type != 'Numeric') {
68412                     numericAxis = false;
68413                     return;
68414                 }
68415                 numericAxis = (numericAxis && axis.type == 'Numeric');
68416                 if (axis) {
68417                     ends = axis.calcEnds();
68418                     if (axis.position == 'top' || axis.position == 'bottom') {
68419                         minX = ends.from;
68420                         maxX = ends.to;
68421                     }
68422                     else {
68423                         minY = ends.from;
68424                         maxY = ends.to;
68425                     }
68426                 }
68427             }
68428         });
68429         
68430         //If there's only one axis specified for a series, then we set the default type of the other
68431         //axis to a category axis. So in this case numericAxis, which would be true if both axes affecting
68432         //the series are numeric should be false.
68433         if (numericAxis && axisCount == 1) {
68434             numericAxis = false;
68435         }
68436         
68437         // If a field was specified without a corresponding axis, create one to get bounds
68438         //only do this for the axis where real values are bound (that's why we check for
68439         //me.axis)
68440         if (me.xField && !Ext.isNumber(minX)) {
68441             if (me.axis == 'bottom' || me.axis == 'top') {
68442                 axis = Ext.create('Ext.chart.axis.Axis', {
68443                     chart: chart,
68444                     fields: [].concat(me.xField)
68445                 }).calcEnds();
68446                 minX = axis.from;
68447                 maxX = axis.to;
68448             } else if (numericAxis) {
68449                 axis = Ext.create('Ext.chart.axis.Axis', {
68450                     chart: chart,
68451                     fields: [].concat(me.xField),
68452                     forceMinMax: true
68453                 }).calcEnds();
68454                 minX = axis.from;
68455                 maxX = axis.to;
68456             }
68457         }
68458         
68459         if (me.yField && !Ext.isNumber(minY)) {
68460             if (me.axis == 'right' || me.axis == 'left') {
68461                 axis = Ext.create('Ext.chart.axis.Axis', {
68462                     chart: chart,
68463                     fields: [].concat(me.yField)
68464                 }).calcEnds();
68465                 minY = axis.from;
68466                 maxY = axis.to;
68467             } else if (numericAxis) {
68468                 axis = Ext.create('Ext.chart.axis.Axis', {
68469                     chart: chart,
68470                     fields: [].concat(me.yField),
68471                     forceMinMax: true
68472                 }).calcEnds();
68473                 minY = axis.from;
68474                 maxY = axis.to;
68475             }
68476         }
68477         
68478         if (isNaN(minX)) {
68479             minX = 0;
68480             xScale = bbox.width / (store.getCount() - 1);
68481         }
68482         else {
68483             xScale = bbox.width / (maxX - minX);
68484         }
68485
68486         if (isNaN(minY)) {
68487             minY = 0;
68488             yScale = bbox.height / (store.getCount() - 1);
68489         } 
68490         else {
68491             yScale = bbox.height / (maxY - minY);
68492         }
68493         
68494         store.each(function(record, i) {
68495             xValue = record.get(me.xField);
68496             yValue = record.get(me.yField);
68497             //skip undefined values
68498             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
68499                 if (Ext.isDefined(Ext.global.console)) {
68500                     Ext.global.console.warn("[Ext.chart.series.Line]  Skipping a store element with an undefined value at ", record, xValue, yValue);
68501                 }
68502                 return;
68503             }
68504             // Ensure a value
68505             if (typeof xValue == 'string' || typeof xValue == 'object'
68506                 //set as uniform distribution if the axis is a category axis.
68507                 || (me.axis != 'top' && me.axis != 'bottom' && !numericAxis)) {
68508                 xValue = i;
68509             }
68510             if (typeof yValue == 'string' || typeof yValue == 'object'
68511                 //set as uniform distribution if the axis is a category axis.
68512                 || (me.axis != 'left' && me.axis != 'right' && !numericAxis)) {
68513                 yValue = i;
68514             }
68515             xValues.push(xValue);
68516             yValues.push(yValue);
68517         }, me);
68518
68519         ln = xValues.length;
68520         if (ln > bbox.width) {
68521             coords = me.shrink(xValues, yValues, bbox.width);
68522             xValues = coords.x;
68523             yValues = coords.y;
68524         }
68525
68526         me.items = [];
68527
68528         count = 0;
68529         ln = xValues.length;
68530         for (i = 0; i < ln; i++) {
68531             xValue = xValues[i];
68532             yValue = yValues[i];
68533             if (yValue === false) {
68534                 if (path.length == 1) {
68535                     path = [];
68536                 }
68537                 onbreak = true;
68538                 me.items.push(false);
68539                 continue;
68540             } else {
68541                 x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
68542                 y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
68543                 if (onbreak) {
68544                     onbreak = false;
68545                     path.push('M');
68546                 } 
68547                 path = path.concat([x, y]);
68548             }
68549             if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
68550                 firstY = y;
68551             }
68552             // If this is the first line, create a dummypath to animate in from.
68553             if (!me.line || chart.resizing) {
68554                 dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
68555             }
68556
68557             // When resizing, reset before animating
68558             if (chart.animate && chart.resizing && me.line) {
68559                 me.line.setAttributes({
68560                     path: dummyPath
68561                 }, true);
68562                 if (me.fillPath) {
68563                     me.fillPath.setAttributes({
68564                         path: dummyPath,
68565                         opacity: 0.2
68566                     }, true);
68567                 }
68568                 if (me.line.shadows) {
68569                     shadows = me.line.shadows;
68570                     for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
68571                         shadow = shadows[j];
68572                         shadow.setAttributes({
68573                             path: dummyPath
68574                         }, true);
68575                     }
68576                 }
68577             }
68578             if (showMarkers) {
68579                 marker = markerGroup.getAt(count++);
68580                 if (!marker) {
68581                     marker = Ext.chart.Shape[type](surface, Ext.apply({
68582                         group: [group, markerGroup],
68583                         x: 0, y: 0,
68584                         translate: {
68585                             x: prevX || x, 
68586                             y: prevY || (bbox.y + bbox.height / 2)
68587                         },
68588                         value: '"' + xValue + ', ' + yValue + '"'
68589                     }, endMarkerStyle));
68590                     marker._to = {
68591                         translate: {
68592                             x: x,
68593                             y: y
68594                         }
68595                     };
68596                 } else {
68597                     marker.setAttributes({
68598                         value: '"' + xValue + ', ' + yValue + '"',
68599                         x: 0, y: 0,
68600                         hidden: false
68601                     }, true);
68602                     marker._to = {
68603                         translate: {
68604                             x: x, y: y
68605                         }
68606                     };
68607                 }
68608             }
68609             me.items.push({
68610                 series: me,
68611                 value: [xValue, yValue],
68612                 point: [x, y],
68613                 sprite: marker,
68614                 storeItem: store.getAt(i)
68615             });
68616             prevX = x;
68617             prevY = y;
68618         }
68619         
68620         if (path.length <= 1) {
68621             //nothing to be rendered
68622             return;    
68623         }
68624         
68625         if (me.smooth) {
68626             path = Ext.draw.Draw.smooth(path, 6);
68627         }
68628         
68629         //Correct path if we're animating timeAxis intervals
68630         if (chart.markerIndex && me.previousPath) {
68631             fromPath = me.previousPath;
68632             fromPath.splice(1, 2);
68633         } else {
68634             fromPath = path;
68635         }
68636
68637         // Only create a line if one doesn't exist.
68638         if (!me.line) {
68639             me.line = surface.add(Ext.apply({
68640                 type: 'path',
68641                 group: group,
68642                 path: dummyPath,
68643                 stroke: endLineStyle.stroke || endLineStyle.fill
68644             }, endLineStyle || {}));
68645             //unset fill here (there's always a default fill withing the themes).
68646             me.line.setAttributes({
68647                 fill: 'none'
68648             });
68649             if (!endLineStyle.stroke && colorArrayLength) {
68650                 me.line.setAttributes({
68651                     stroke: colorArrayStyle[seriesIdx % colorArrayLength]
68652                 }, true);
68653             }
68654             if (enableShadows) {
68655                 //create shadows
68656                 shadows = me.line.shadows = [];                
68657                 for (shindex = 0; shindex < lnsh; shindex++) {
68658                     shadowBarAttr = shadowAttributes[shindex];
68659                     shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
68660                     shadow = chart.surface.add(Ext.apply({}, {
68661                         type: 'path',
68662                         group: shadowGroups[shindex]
68663                     }, shadowBarAttr));
68664                     shadows.push(shadow);
68665                 }
68666             }
68667         }
68668         if (me.fill) {
68669             fillPath = path.concat([
68670                 ["L", x, bbox.y + bbox.height],
68671                 ["L", bbox.x, bbox.y + bbox.height],
68672                 ["L", bbox.x, firstY]
68673             ]);
68674             if (!me.fillPath) {
68675                 me.fillPath = surface.add({
68676                     group: group,
68677                     type: 'path',
68678                     opacity: endLineStyle.opacity || 0.3,
68679                     fill: colorArrayStyle[seriesIdx % colorArrayLength] || endLineStyle.fill,
68680                     path: dummyPath
68681                 });
68682             }
68683         }
68684         markerCount = showMarkers && markerGroup.getCount();
68685         if (chart.animate) {
68686             fill = me.fill;
68687             line = me.line;
68688             //Add renderer to line. There is not unique record associated with this.
68689             rendererAttributes = me.renderer(line, false, { path: path }, i, store);
68690             Ext.apply(rendererAttributes, endLineStyle || {}, {
68691                 stroke: endLineStyle.stroke || endLineStyle.fill
68692             });
68693             //fill should not be used here but when drawing the special fill path object
68694             delete rendererAttributes.fill;
68695             if (chart.markerIndex && me.previousPath) {
68696                 me.animation = animation = me.onAnimate(line, {
68697                     to: rendererAttributes,
68698                     from: {
68699                         path: fromPath
68700                     }
68701                 });
68702             } else {
68703                 me.animation = animation = me.onAnimate(line, {
68704                     to: rendererAttributes
68705                 });
68706             }
68707             //animate shadows
68708             if (enableShadows) {
68709                 shadows = line.shadows;
68710                 for(j = 0; j < lnsh; j++) {
68711                     if (chart.markerIndex && me.previousPath) {
68712                         me.onAnimate(shadows[j], {
68713                             to: { path: path },
68714                             from: { path: fromPath }
68715                         });
68716                     } else {
68717                         me.onAnimate(shadows[j], {
68718                             to: { path: path }
68719                         });
68720                     }
68721                 }
68722             }
68723             //animate fill path
68724             if (fill) {
68725                 me.onAnimate(me.fillPath, {
68726                     to: Ext.apply({}, {
68727                         path: fillPath,
68728                         fill: colorArrayStyle[seriesIdx % colorArrayLength] || endLineStyle.fill
68729                     }, endLineStyle || {})
68730                 });
68731             }
68732             //animate markers
68733             if (showMarkers) {
68734                 count = 0;
68735                 for(i = 0; i < ln; i++) {
68736                     if (me.items[i]) {
68737                         item = markerGroup.getAt(count++);
68738                         if (item) {
68739                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
68740                             me.onAnimate(item, {
68741                                 to: Ext.apply(rendererAttributes, endMarkerStyle || {})
68742                             });
68743                         }
68744                     } 
68745                 }
68746                 for(; count < markerCount; count++) {
68747                     item = markerGroup.getAt(count);
68748                     item.hide(true);
68749                 }
68750             }
68751         } else {
68752             rendererAttributes = me.renderer(me.line, false, { path: path, hidden: false }, i, store);
68753             Ext.apply(rendererAttributes, endLineStyle || {}, {
68754                 stroke: endLineStyle.stroke || endLineStyle.fill
68755             });
68756             //fill should not be used here but when drawing the special fill path object
68757             delete rendererAttributes.fill;
68758             me.line.setAttributes(rendererAttributes, true);
68759             //set path for shadows
68760             if (enableShadows) {
68761                 shadows = me.line.shadows;
68762                 for(j = 0; j < lnsh; j++) {
68763                     shadows[j].setAttributes({
68764                         path: path
68765                     }, true);
68766                 }
68767             }
68768             if (me.fill) {
68769                 me.fillPath.setAttributes({
68770                     path: fillPath
68771                 }, true);
68772             }
68773             if (showMarkers) {
68774                 count = 0;
68775                 for(i = 0; i < ln; i++) {
68776                     if (me.items[i]) {
68777                         item = markerGroup.getAt(count++);
68778                         if (item) {
68779                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
68780                             item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
68781                         }
68782                     } 
68783                 }
68784                 for(; count < markerCount; count++) {
68785                     item = markerGroup.getAt(count);
68786                     item.hide(true);
68787                 }
68788             }
68789         }
68790
68791         if (chart.markerIndex) {
68792             path.splice(1, 0, path[1], path[2]);
68793             me.previousPath = path;
68794         }
68795         me.renderLabels();
68796         me.renderCallouts();
68797     },
68798     
68799     // @private called when a label is to be created.
68800     onCreateLabel: function(storeItem, item, i, display) {
68801         var me = this,
68802             group = me.labelsGroup,
68803             config = me.label,
68804             bbox = me.bbox,
68805             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
68806
68807         return me.chart.surface.add(Ext.apply({
68808             'type': 'text',
68809             'text-anchor': 'middle',
68810             'group': group,
68811             'x': item.point[0],
68812             'y': bbox.y + bbox.height / 2
68813         }, endLabelStyle || {}));
68814     },
68815     
68816     // @private called when a label is to be created.
68817     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
68818         var me = this,
68819             chart = me.chart,
68820             resizing = chart.resizing,
68821             config = me.label,
68822             format = config.renderer,
68823             field = config.field,
68824             bbox = me.bbox,
68825             x = item.point[0],
68826             y = item.point[1],
68827             radius = item.sprite.attr.radius,
68828             bb, width, height;
68829         
68830         label.setAttributes({
68831             text: format(storeItem.get(field)),
68832             hidden: true
68833         }, true);
68834         
68835         if (display == 'rotate') {
68836             label.setAttributes({
68837                 'text-anchor': 'start',
68838                 'rotation': {
68839                     x: x,
68840                     y: y,
68841                     degrees: -45
68842                 }
68843             }, true);
68844             //correct label position to fit into the box
68845             bb = label.getBBox();
68846             width = bb.width;
68847             height = bb.height;
68848             x = x < bbox.x? bbox.x : x;
68849             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
68850             y = (y - height < bbox.y)? bbox.y + height : y;
68851         
68852         } else if (display == 'under' || display == 'over') {
68853             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
68854             bb = item.sprite.getBBox();
68855             bb.width = bb.width || (radius * 2);
68856             bb.height = bb.height || (radius * 2);
68857             y = y + (display == 'over'? -bb.height : bb.height);
68858             //correct label position to fit into the box
68859             bb = label.getBBox();
68860             width = bb.width/2;
68861             height = bb.height/2;
68862             x = x - width < bbox.x? bbox.x + width : x;
68863             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
68864             y = y - height < bbox.y? bbox.y + height : y;
68865             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
68866         }
68867         
68868         if (me.chart.animate && !me.chart.resizing) {
68869             label.show(true);
68870             me.onAnimate(label, {
68871                 to: {
68872                     x: x,
68873                     y: y
68874                 }
68875             });
68876         } else {
68877             label.setAttributes({
68878                 x: x,
68879                 y: y
68880             }, true);
68881             if (resizing) {
68882                 me.animation.on('afteranimate', function() {
68883                     label.show(true);
68884                 });
68885             } else {
68886                 label.show(true);
68887             }
68888         }
68889     },
68890
68891     //@private Overriding highlights.js highlightItem method.
68892     highlightItem: function() {
68893         var me = this;
68894         me.callParent(arguments);
68895         if (this.line && !this.highlighted) {
68896             if (!('__strokeWidth' in this.line)) {
68897                 this.line.__strokeWidth = this.line.attr['stroke-width'] || 0;
68898             }
68899             if (this.line.__anim) {
68900                 this.line.__anim.paused = true;
68901             }
68902             this.line.__anim = Ext.create('Ext.fx.Anim', {
68903                 target: this.line,
68904                 to: {
68905                     'stroke-width': this.line.__strokeWidth + 3
68906                 }
68907             });
68908             this.highlighted = true;
68909         }
68910     },
68911
68912     //@private Overriding highlights.js unHighlightItem method.
68913     unHighlightItem: function() {
68914         var me = this;
68915         me.callParent(arguments);
68916         if (this.line && this.highlighted) {
68917             this.line.__anim = Ext.create('Ext.fx.Anim', {
68918                 target: this.line,
68919                 to: {
68920                     'stroke-width': this.line.__strokeWidth
68921                 }
68922             });
68923             this.highlighted = false;
68924         }
68925     },
68926
68927     //@private called when a callout needs to be placed.
68928     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
68929         if (!display) {
68930             return;
68931         }
68932         
68933         var me = this,
68934             chart = me.chart,
68935             surface = chart.surface,
68936             resizing = chart.resizing,
68937             config = me.callouts,
68938             items = me.items,
68939             prev = i == 0? false : items[i -1].point,
68940             next = (i == items.length -1)? false : items[i +1].point,
68941             cur = [+item.point[0], +item.point[1]],
68942             dir, norm, normal, a, aprev, anext,
68943             offsetFromViz = config.offsetFromViz || 30,
68944             offsetToSide = config.offsetToSide || 10,
68945             offsetBox = config.offsetBox || 3,
68946             boxx, boxy, boxw, boxh,
68947             p, clipRect = me.clipRect,
68948             bbox = {
68949                 width: config.styles.width || 10,
68950                 height: config.styles.height || 10
68951             },
68952             x, y;
68953
68954         //get the right two points
68955         if (!prev) {
68956             prev = cur;
68957         }
68958         if (!next) {
68959             next = cur;
68960         }
68961         a = (next[1] - prev[1]) / (next[0] - prev[0]);
68962         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
68963         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
68964         
68965         norm = Math.sqrt(1 + a * a);
68966         dir = [1 / norm, a / norm];
68967         normal = [-dir[1], dir[0]];
68968         
68969         //keep the label always on the outer part of the "elbow"
68970         if (aprev > 0 && anext < 0 && normal[1] < 0
68971             || aprev < 0 && anext > 0 && normal[1] > 0) {
68972             normal[0] *= -1;
68973             normal[1] *= -1;
68974         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0
68975                    || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
68976             normal[0] *= -1;
68977             normal[1] *= -1;
68978         }
68979         //position
68980         x = cur[0] + normal[0] * offsetFromViz;
68981         y = cur[1] + normal[1] * offsetFromViz;
68982
68983         //box position and dimensions
68984         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
68985         boxy = y - bbox.height /2 - offsetBox;
68986         boxw = bbox.width + 2 * offsetBox;
68987         boxh = bbox.height + 2 * offsetBox;
68988         
68989         //now check if we're out of bounds and invert the normal vector correspondingly
68990         //this may add new overlaps between labels (but labels won't be out of bounds).
68991         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
68992             normal[0] *= -1;
68993         }
68994         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
68995             normal[1] *= -1;
68996         }
68997
68998         //update positions
68999         x = cur[0] + normal[0] * offsetFromViz;
69000         y = cur[1] + normal[1] * offsetFromViz;
69001         
69002         //update box position and dimensions
69003         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
69004         boxy = y - bbox.height /2 - offsetBox;
69005         boxw = bbox.width + 2 * offsetBox;
69006         boxh = bbox.height + 2 * offsetBox;
69007         
69008         if (chart.animate) {
69009             //set the line from the middle of the pie to the box.
69010             me.onAnimate(callout.lines, {
69011                 to: {
69012                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
69013                 }
69014             });
69015             //set component position
69016             if (callout.panel) {
69017                 callout.panel.setPosition(boxx, boxy, true);
69018             }
69019         }
69020         else {
69021             //set the line from the middle of the pie to the box.
69022             callout.lines.setAttributes({
69023                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
69024             }, true);
69025             //set component position
69026             if (callout.panel) {
69027                 callout.panel.setPosition(boxx, boxy);
69028             }
69029         }
69030         for (p in callout) {
69031             callout[p].show(true);
69032         }
69033     },
69034     
69035     isItemInPoint: function(x, y, item, i) {
69036         var me = this,
69037             items = me.items,
69038             tolerance = me.selectionTolerance,
69039             result = null,
69040             prevItem,
69041             nextItem,
69042             prevPoint,
69043             nextPoint,
69044             ln,
69045             x1,
69046             y1,
69047             x2,
69048             y2,
69049             xIntersect,
69050             yIntersect,
69051             dist1, dist2, dist, midx, midy,
69052             sqrt = Math.sqrt, abs = Math.abs;
69053         
69054         nextItem = items[i];
69055         prevItem = i && items[i - 1];
69056         
69057         if (i >= ln) {
69058             prevItem = items[ln - 1];
69059         }
69060         prevPoint = prevItem && prevItem.point;
69061         nextPoint = nextItem && nextItem.point;
69062         x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
69063         y1 = prevItem ? prevPoint[1] : nextPoint[1];
69064         x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
69065         y2 = nextItem ? nextPoint[1] : prevPoint[1];
69066         dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
69067         dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
69068         dist = Math.min(dist1, dist2);
69069         
69070         if (dist <= tolerance) {
69071             return dist == dist1? prevItem : nextItem;
69072         }
69073         return false;
69074     },
69075     
69076     // @private toggle visibility of all series elements (markers, sprites).
69077     toggleAll: function(show) {
69078         var me = this,
69079             i, ln, shadow, shadows;
69080         if (!show) {
69081             Ext.chart.series.Line.superclass.hideAll.call(me);
69082         }
69083         else {
69084             Ext.chart.series.Line.superclass.showAll.call(me);
69085         }
69086         if (me.line) {
69087             me.line.setAttributes({
69088                 hidden: !show
69089             }, true);
69090             //hide shadows too
69091             if (me.line.shadows) {
69092                 for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
69093                     shadow = shadows[i];
69094                     shadow.setAttributes({
69095                         hidden: !show
69096                     }, true);
69097                 }
69098             }
69099         }
69100         if (me.fillPath) {
69101             me.fillPath.setAttributes({
69102                 hidden: !show
69103             }, true);
69104         }
69105     },
69106     
69107     // @private hide all series elements (markers, sprites).
69108     hideAll: function() {
69109         this.toggleAll(false);
69110     },
69111     
69112     // @private hide all series elements (markers, sprites).
69113     showAll: function() {
69114         this.toggleAll(true);
69115     }
69116 });
69117 /**
69118  * @class Ext.chart.series.Pie
69119  * @extends Ext.chart.series.Series
69120  * 
69121  * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different 
69122  * categories that also have a meaning as a whole.
69123  * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart 
69124  * documentation for more information. A typical configuration object for the pie series could be:
69125  * 
69126  * {@img Ext.chart.series.Pie/Ext.chart.series.Pie.png Ext.chart.series.Pie chart series}
69127  *
69128  *     var store = Ext.create('Ext.data.JsonStore', {
69129  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
69130  *         data: [
69131  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
69132  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
69133  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
69134  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
69135  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
69136  *         ]
69137  *     });
69138  *     
69139  *     Ext.create('Ext.chart.Chart', {
69140  *         renderTo: Ext.getBody(),
69141  *         width: 500,
69142  *         height: 300,
69143  *         animate: true,
69144  *         store: store,
69145  *         theme: 'Base:gradients',
69146  *         series: [{
69147  *             type: 'pie',
69148  *             field: 'data1',
69149  *             showInLegend: true,
69150  *             tips: {
69151  *               trackMouse: true,
69152  *               width: 140,
69153  *               height: 28,
69154  *               renderer: function(storeItem, item) {
69155  *                 //calculate and display percentage on hover
69156  *                 var total = 0;
69157  *                 store.each(function(rec) {
69158  *                     total += rec.get('data1');
69159  *                 });
69160  *                 this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%');
69161  *               }
69162  *             },
69163  *             highlight: {
69164  *               segment: {
69165  *                 margin: 20
69166  *               }
69167  *             },
69168  *             label: {
69169  *                 field: 'name',
69170  *                 display: 'rotate',
69171  *                 contrast: true,
69172  *                 font: '18px Arial'
69173  *             }
69174  *         }]    
69175  *     });
69176  * 
69177  * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options 
69178  * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item. 
69179  * 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 
69180  * 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. 
69181  * 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 
69182  * and size through the `font` parameter. 
69183  * 
69184  * @xtype pie
69185  */
69186 Ext.define('Ext.chart.series.Pie', {
69187
69188     /* Begin Definitions */
69189
69190     alternateClassName: ['Ext.chart.PieSeries', 'Ext.chart.PieChart'],
69191
69192     extend: 'Ext.chart.series.Series',
69193
69194     /* End Definitions */
69195
69196     type: "pie",
69197     
69198     alias: 'series.pie',
69199
69200     rad: Math.PI / 180,
69201
69202     /**
69203      * @cfg {Number} highlightDuration
69204      * The duration for the pie slice highlight effect.
69205      */
69206     highlightDuration: 150,
69207
69208     /**
69209      * @cfg {String} angleField
69210      * The store record field name to be used for the pie angles.
69211      * The values bound to this field name must be positive real numbers.
69212      * This parameter is required.
69213      */
69214     angleField: false,
69215
69216     /**
69217      * @cfg {String} lengthField
69218      * The store record field name to be used for the pie slice lengths.
69219      * The values bound to this field name must be positive real numbers.
69220      * This parameter is optional.
69221      */
69222     lengthField: false,
69223
69224     /**
69225      * @cfg {Boolean|Number} donut
69226      * Whether to set the pie chart as donut chart.
69227      * Default's false. Can be set to a particular percentage to set the radius
69228      * of the donut chart.
69229      */
69230     donut: false,
69231
69232     /**
69233      * @cfg {Boolean} showInLegend
69234      * Whether to add the pie chart elements as legend items. Default's false.
69235      */
69236     showInLegend: false,
69237
69238     /**
69239      * @cfg {Array} colorSet
69240      * An array of color values which will be used, in order, as the pie slice fill colors.
69241      */
69242     
69243     /**
69244      * @cfg {Object} style
69245      * An object containing styles for overriding series styles from Theming.
69246      */
69247     style: {},
69248     
69249     constructor: function(config) {
69250         this.callParent(arguments);
69251         var me = this,
69252             chart = me.chart,
69253             surface = chart.surface,
69254             store = chart.store,
69255             shadow = chart.shadow, i, l, cfg;
69256         Ext.applyIf(me, {
69257             highlightCfg: {
69258                 segment: {
69259                     margin: 20
69260                 }
69261             }
69262         });
69263         Ext.apply(me, config, {            
69264             shadowAttributes: [{
69265                 "stroke-width": 6,
69266                 "stroke-opacity": 1,
69267                 stroke: 'rgb(200, 200, 200)',
69268                 translate: {
69269                     x: 1.2,
69270                     y: 2
69271                 }
69272             },
69273             {
69274                 "stroke-width": 4,
69275                 "stroke-opacity": 1,
69276                 stroke: 'rgb(150, 150, 150)',
69277                 translate: {
69278                     x: 0.9,
69279                     y: 1.5
69280                 }
69281             },
69282             {
69283                 "stroke-width": 2,
69284                 "stroke-opacity": 1,
69285                 stroke: 'rgb(100, 100, 100)',
69286                 translate: {
69287                     x: 0.6,
69288                     y: 1
69289                 }
69290             }]
69291         });
69292         me.group = surface.getGroup(me.seriesId);
69293         if (shadow) {
69294             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
69295                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
69296             }
69297         }
69298         surface.customAttributes.segment = function(opt) {
69299             return me.getSegment(opt);
69300         };
69301     },
69302     
69303     //@private updates some onbefore render parameters.
69304     initialize: function() {
69305         var me = this,
69306             store = me.chart.substore || me.chart.store;
69307         //Add yFields to be used in Legend.js
69308         me.yField = [];
69309         if (me.label.field) {
69310             store.each(function(rec) {
69311                 me.yField.push(rec.get(me.label.field));
69312             });
69313         }
69314     },
69315
69316     // @private returns an object with properties for a PieSlice.
69317     getSegment: function(opt) {
69318         var me = this,
69319             rad = me.rad,
69320             cos = Math.cos,
69321             sin = Math.sin,
69322             abs = Math.abs,
69323             x = me.centerX,
69324             y = me.centerY,
69325             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
69326             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
69327             delta = 1e-2,
69328             r = opt.endRho - opt.startRho,
69329             startAngle = opt.startAngle,
69330             endAngle = opt.endAngle,
69331             midAngle = (startAngle + endAngle) / 2 * rad,
69332             margin = opt.margin || 0,
69333             flag = abs(endAngle - startAngle) > 180,
69334             a1 = Math.min(startAngle, endAngle) * rad,
69335             a2 = Math.max(startAngle, endAngle) * rad,
69336             singleSlice = false;
69337
69338         x += margin * cos(midAngle);
69339         y += margin * sin(midAngle);
69340
69341         x1 = x + opt.startRho * cos(a1);
69342         y1 = y + opt.startRho * sin(a1);
69343
69344         x2 = x + opt.endRho * cos(a1);
69345         y2 = y + opt.endRho * sin(a1);
69346
69347         x3 = x + opt.startRho * cos(a2);
69348         y3 = y + opt.startRho * sin(a2);
69349
69350         x4 = x + opt.endRho * cos(a2);
69351         y4 = y + opt.endRho * sin(a2);
69352
69353         if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
69354             singleSlice = true;
69355         }
69356         //Solves mysterious clipping bug with IE
69357         if (singleSlice) {
69358             return {
69359                 path: [
69360                 ["M", x1, y1],
69361                 ["L", x2, y2],
69362                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
69363                 ["Z"]]
69364             };
69365         } else {
69366             return {
69367                 path: [
69368                 ["M", x1, y1],
69369                 ["L", x2, y2],
69370                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
69371                 ["L", x3, y3],
69372                 ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
69373                 ["Z"]]
69374             };
69375         }
69376     },
69377
69378     // @private utility function to calculate the middle point of a pie slice.
69379     calcMiddle: function(item) {
69380         var me = this,
69381             rad = me.rad,
69382             slice = item.slice,
69383             x = me.centerX,
69384             y = me.centerY,
69385             startAngle = slice.startAngle,
69386             endAngle = slice.endAngle,
69387             donut = +me.donut,
69388             a1 = Math.min(startAngle, endAngle) * rad,
69389             a2 = Math.max(startAngle, endAngle) * rad,
69390             midAngle = -(a1 + (a2 - a1) / 2),
69391             xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
69392             ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
69393
69394         item.middle = {
69395             x: xm,
69396             y: ym
69397         };
69398     },
69399
69400     /**
69401      * Draws the series for the current chart.
69402      */
69403     drawSeries: function() {
69404         var me = this,
69405             store = me.chart.substore || me.chart.store,
69406             group = me.group,
69407             animate = me.chart.animate,
69408             field = me.angleField || me.field || me.xField,
69409             lenField = [].concat(me.lengthField),
69410             totalLenField = 0,
69411             colors = me.colorSet,
69412             chart = me.chart,
69413             surface = chart.surface,
69414             chartBBox = chart.chartBBox,
69415             enableShadows = chart.shadow,
69416             shadowGroups = me.shadowGroups,
69417             shadowAttributes = me.shadowAttributes,
69418             lnsh = shadowGroups.length,
69419             rad = me.rad,
69420             layers = lenField.length,
69421             rhoAcum = 0,
69422             donut = +me.donut,
69423             layerTotals = [],
69424             values = {},
69425             fieldLength,
69426             items = [],
69427             passed = false,
69428             totalField = 0,
69429             maxLenField = 0,
69430             cut = 9,
69431             defcut = true,
69432             angle = 0,
69433             seriesStyle = me.seriesStyle,
69434             seriesLabelStyle = me.seriesLabelStyle,
69435             colorArrayStyle = me.colorArrayStyle,
69436             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
69437             gutterX = chart.maxGutter[0],
69438             gutterY = chart.maxGutter[1],
69439             rendererAttributes,
69440             shadowGroup,
69441             shadowAttr,
69442             shadows,
69443             shadow,
69444             shindex,
69445             centerX,
69446             centerY,
69447             deltaRho,
69448             first = 0,
69449             slice,
69450             slices,
69451             sprite,
69452             value,
69453             item,
69454             lenValue,
69455             ln,
69456             record,
69457             i,
69458             j,
69459             startAngle,
69460             endAngle,
69461             middleAngle,
69462             sliceLength,
69463             path,
69464             p,
69465             spriteOptions, bbox;
69466         
69467         Ext.apply(seriesStyle, me.style || {});
69468
69469         me.setBBox();
69470         bbox = me.bbox;
69471
69472         //override theme colors
69473         if (me.colorSet) {
69474             colorArrayStyle = me.colorSet;
69475             colorArrayLength = colorArrayStyle.length;
69476         }
69477         
69478         //if not store or store is empty then there's nothing to draw
69479         if (!store || !store.getCount()) {
69480             return;
69481         }
69482         
69483         me.unHighlightItem();
69484         me.cleanHighlights();
69485
69486         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
69487         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
69488         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
69489         me.slices = slices = [];
69490         me.items = items = [];
69491
69492         store.each(function(record, i) {
69493             if (this.__excludes && this.__excludes[i]) {
69494                 //hidden series
69495                 return;
69496             }
69497             totalField += +record.get(field);
69498             if (lenField[0]) {
69499                 for (j = 0, totalLenField = 0; j < layers; j++) {
69500                     totalLenField += +record.get(lenField[j]);
69501                 }
69502                 layerTotals[i] = totalLenField;
69503                 maxLenField = Math.max(maxLenField, totalLenField);
69504             }
69505         }, this);
69506
69507         store.each(function(record, i) {
69508             if (this.__excludes && this.__excludes[i]) {
69509                 //hidden series
69510                 return;
69511             } 
69512             value = record.get(field);
69513             middleAngle = angle - 360 * value / totalField / 2;
69514             // TODO - Put up an empty circle
69515             if (isNaN(middleAngle)) {
69516                 middleAngle = 360;
69517                 value = 1;
69518                 totalField = 1;
69519             }
69520             // First slice
69521             if (!i || first == 0) {
69522                 angle = 360 - middleAngle;
69523                 me.firstAngle = angle;
69524                 middleAngle = angle - 360 * value / totalField / 2;
69525             }
69526             endAngle = angle - 360 * value / totalField;
69527             slice = {
69528                 series: me,
69529                 value: value,
69530                 startAngle: angle,
69531                 endAngle: endAngle,
69532                 storeItem: record
69533             };
69534             if (lenField[0]) {
69535                 lenValue = layerTotals[i];
69536                 slice.rho = me.radius * (lenValue / maxLenField);
69537             } else {
69538                 slice.rho = me.radius;
69539             }
69540             slices[i] = slice;
69541             if((slice.startAngle % 360) == (slice.endAngle % 360)) {
69542                 slice.startAngle -= 0.0001;
69543             }
69544             angle = endAngle;
69545             first++;
69546         }, me);
69547         
69548         //do all shadows first.
69549         if (enableShadows) {
69550             for (i = 0, ln = slices.length; i < ln; i++) {
69551                 if (this.__excludes && this.__excludes[i]) {
69552                     //hidden series
69553                     continue;
69554                 }
69555                 slice = slices[i];
69556                 slice.shadowAttrs = [];
69557                 for (j = 0, rhoAcum = 0, shadows = []; j < layers; j++) {
69558                     sprite = group.getAt(i * layers + j);
69559                     deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
69560                     //set pie slice properties
69561                     rendererAttributes = {
69562                         segment: {
69563                             startAngle: slice.startAngle,
69564                             endAngle: slice.endAngle,
69565                             margin: 0,
69566                             rho: slice.rho,
69567                             startRho: rhoAcum + (deltaRho * donut / 100),
69568                             endRho: rhoAcum + deltaRho
69569                         }
69570                     };
69571                     //create shadows
69572                     for (shindex = 0, shadows = []; shindex < lnsh; shindex++) {
69573                         shadowAttr = shadowAttributes[shindex];
69574                         shadow = shadowGroups[shindex].getAt(i);
69575                         if (!shadow) {
69576                             shadow = chart.surface.add(Ext.apply({}, {
69577                                 type: 'path',
69578                                 group: shadowGroups[shindex],
69579                                 strokeLinejoin: "round"
69580                             }, rendererAttributes, shadowAttr));
69581                         }
69582                         if (animate) {
69583                             shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply({}, rendererAttributes, shadowAttr), i, store);
69584                             me.onAnimate(shadow, {
69585                                 to: shadowAttr
69586                             });
69587                         } else {
69588                             shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply(shadowAttr, {
69589                                 hidden: false
69590                             }), i, store);
69591                             shadow.setAttributes(shadowAttr, true);
69592                         }
69593                         shadows.push(shadow);
69594                     }
69595                     slice.shadowAttrs[j] = shadows;
69596                 }
69597             }
69598         }
69599         //do pie slices after.
69600         for (i = 0, ln = slices.length; i < ln; i++) {
69601             if (this.__excludes && this.__excludes[i]) {
69602                 //hidden series
69603                 continue;
69604             }
69605             slice = slices[i];
69606             for (j = 0, rhoAcum = 0; j < layers; j++) {
69607                 sprite = group.getAt(i * layers + j);
69608                 deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
69609                 //set pie slice properties
69610                 rendererAttributes = Ext.apply({
69611                     segment: {
69612                         startAngle: slice.startAngle,
69613                         endAngle: slice.endAngle,
69614                         margin: 0,
69615                         rho: slice.rho,
69616                         startRho: rhoAcum + (deltaRho * donut / 100),
69617                         endRho: rhoAcum + deltaRho
69618                     } 
69619                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
69620                 item = Ext.apply({},
69621                 rendererAttributes.segment, {
69622                     slice: slice,
69623                     series: me,
69624                     storeItem: slice.storeItem,
69625                     index: i
69626                 });
69627                 me.calcMiddle(item);
69628                 if (enableShadows) {
69629                     item.shadows = slice.shadowAttrs[j];
69630                 }
69631                 items[i] = item;
69632                 // Create a new sprite if needed (no height)
69633                 if (!sprite) {
69634                     spriteOptions = Ext.apply({
69635                         type: "path",
69636                         group: group,
69637                         middle: item.middle
69638                     }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
69639                     sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
69640                 }
69641                 slice.sprite = slice.sprite || [];
69642                 item.sprite = sprite;
69643                 slice.sprite.push(sprite);
69644                 slice.point = [item.middle.x, item.middle.y];
69645                 if (animate) {
69646                     rendererAttributes = me.renderer(sprite, store.getAt(i), rendererAttributes, i, store);
69647                     sprite._to = rendererAttributes;
69648                     sprite._animating = true;
69649                     me.onAnimate(sprite, {
69650                         to: rendererAttributes,
69651                         listeners: {
69652                             afteranimate: {
69653                                 fn: function() {
69654                                     this._animating = false;
69655                                 },
69656                                 scope: sprite
69657                             }
69658                         }
69659                     });
69660                 } else {
69661                     rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(rendererAttributes, {
69662                         hidden: false
69663                     }), i, store);
69664                     sprite.setAttributes(rendererAttributes, true);
69665                 }
69666                 rhoAcum += deltaRho;
69667             }
69668         }
69669         
69670         // Hide unused bars
69671         ln = group.getCount();
69672         for (i = 0; i < ln; i++) {
69673             if (!slices[(i / layers) >> 0] && group.getAt(i)) {
69674                 group.getAt(i).hide(true);
69675             }
69676         }
69677         if (enableShadows) {
69678             lnsh = shadowGroups.length;
69679             for (shindex = 0; shindex < ln; shindex++) {
69680                 if (!slices[(shindex / layers) >> 0]) {
69681                     for (j = 0; j < lnsh; j++) {
69682                         if (shadowGroups[j].getAt(shindex)) {
69683                             shadowGroups[j].getAt(shindex).hide(true);
69684                         }
69685                     }
69686                 }
69687             }
69688         }
69689         me.renderLabels();
69690         me.renderCallouts();
69691     },
69692
69693     // @private callback for when creating a label sprite.
69694     onCreateLabel: function(storeItem, item, i, display) {
69695         var me = this,
69696             group = me.labelsGroup,
69697             config = me.label,
69698             centerX = me.centerX,
69699             centerY = me.centerY,
69700             middle = item.middle,
69701             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {});
69702         
69703         return me.chart.surface.add(Ext.apply({
69704             'type': 'text',
69705             'text-anchor': 'middle',
69706             'group': group,
69707             'x': middle.x,
69708             'y': middle.y
69709         }, endLabelStyle));
69710     },
69711
69712     // @private callback for when placing a label sprite.
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 = [].concat(config.field),
69720             centerX = me.centerX,
69721             centerY = me.centerY,
69722             middle = item.middle,
69723             opt = {
69724                 x: middle.x,
69725                 y: middle.y
69726             },
69727             x = middle.x - centerX,
69728             y = middle.y - centerY,
69729             from = {},
69730             rho = 1,
69731             theta = Math.atan2(y, x || 1),
69732             dg = theta * 180 / Math.PI,
69733             prevDg;
69734         
69735         function fixAngle(a) {
69736             if (a < 0) a += 360;
69737             return a % 360;
69738         }
69739
69740         label.setAttributes({
69741             text: format(storeItem.get(field[index]))
69742         }, true);
69743
69744         switch (display) {
69745         case 'outside':
69746             rho = Math.sqrt(x * x + y * y) * 2;
69747             //update positions
69748             opt.x = rho * Math.cos(theta) + centerX;
69749             opt.y = rho * Math.sin(theta) + centerY;
69750             break;
69751
69752         case 'rotate':
69753             dg = fixAngle(dg);
69754             dg = (dg > 90 && dg < 270) ? dg + 180: dg;
69755
69756             prevDg = label.attr.rotation.degrees;
69757             if (prevDg != null && Math.abs(prevDg - dg) > 180) {
69758                 if (dg > prevDg) {
69759                     dg -= 360;
69760                 } else {
69761                     dg += 360;
69762                 }
69763                 dg = dg % 360;
69764             } else {
69765                 dg = fixAngle(dg);
69766             }
69767             //update rotation angle
69768             opt.rotate = {
69769                 degrees: dg,
69770                 x: opt.x,
69771                 y: opt.y
69772             };
69773             break;
69774
69775         default:
69776             break;
69777         }
69778         //ensure the object has zero translation
69779         opt.translate = {
69780             x: 0, y: 0    
69781         };
69782         if (animate && !resizing && (display != 'rotate' || prevDg != null)) {
69783             me.onAnimate(label, {
69784                 to: opt
69785             });
69786         } else {
69787             label.setAttributes(opt, true);
69788         }
69789         label._from = from;
69790     },
69791
69792     // @private callback for when placing a callout sprite.
69793     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
69794         var me = this,
69795             chart = me.chart,
69796             resizing = chart.resizing,
69797             config = me.callouts,
69798             centerX = me.centerX,
69799             centerY = me.centerY,
69800             middle = item.middle,
69801             opt = {
69802                 x: middle.x,
69803                 y: middle.y
69804             },
69805             x = middle.x - centerX,
69806             y = middle.y - centerY,
69807             rho = 1,
69808             rhoCenter,
69809             theta = Math.atan2(y, x || 1),
69810             bbox = callout.label.getBBox(),
69811             offsetFromViz = 20,
69812             offsetToSide = 10,
69813             offsetBox = 10,
69814             p;
69815
69816         //should be able to config this.
69817         rho = item.endRho + offsetFromViz;
69818         rhoCenter = (item.endRho + item.startRho) / 2 + (item.endRho - item.startRho) / 3;
69819         //update positions
69820         opt.x = rho * Math.cos(theta) + centerX;
69821         opt.y = rho * Math.sin(theta) + centerY;
69822
69823         x = rhoCenter * Math.cos(theta);
69824         y = rhoCenter * Math.sin(theta);
69825
69826         if (chart.animate) {
69827             //set the line from the middle of the pie to the box.
69828             me.onAnimate(callout.lines, {
69829                 to: {
69830                     path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
69831                 }
69832             });
69833             //set box position
69834             me.onAnimate(callout.box, {
69835                 to: {
69836                     x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
69837                     y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
69838                     width: bbox.width + 2 * offsetBox,
69839                     height: bbox.height + 2 * offsetBox
69840                 }
69841             });
69842             //set text position
69843             me.onAnimate(callout.label, {
69844                 to: {
69845                     x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
69846                     y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
69847                 }
69848             });
69849         } else {
69850             //set the line from the middle of the pie to the box.
69851             callout.lines.setAttributes({
69852                 path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
69853             },
69854             true);
69855             //set box position
69856             callout.box.setAttributes({
69857                 x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
69858                 y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
69859                 width: bbox.width + 2 * offsetBox,
69860                 height: bbox.height + 2 * offsetBox
69861             },
69862             true);
69863             //set text position
69864             callout.label.setAttributes({
69865                 x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
69866                 y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
69867             },
69868             true);
69869         }
69870         for (p in callout) {
69871             callout[p].show(true);
69872         }
69873     },
69874
69875     // @private handles sprite animation for the series.
69876     onAnimate: function(sprite, attr) {
69877         sprite.show();
69878         return this.callParent(arguments);
69879     },
69880
69881     isItemInPoint: function(x, y, item, i) {
69882         var me = this,
69883             cx = me.centerX,
69884             cy = me.centerY,
69885             abs = Math.abs,
69886             dx = abs(x - cx),
69887             dy = abs(y - cy),
69888             startAngle = item.startAngle,
69889             endAngle = item.endAngle,
69890             rho = Math.sqrt(dx * dx + dy * dy),
69891             angle = Math.atan2(y - cy, x - cx) / me.rad + 360;
69892         
69893         // normalize to the same range of angles created by drawSeries
69894         if (angle > me.firstAngle) {
69895             angle -= 360;
69896         }
69897         return (angle <= startAngle && angle > endAngle
69898                 && rho >= item.startRho && rho <= item.endRho);
69899     },
69900     
69901     // @private hides all elements in the series.
69902     hideAll: function() {
69903         var i, l, shadow, shadows, sh, lsh, sprite;
69904         if (!isNaN(this._index)) {
69905             this.__excludes = this.__excludes || [];
69906             this.__excludes[this._index] = true;
69907             sprite = this.slices[this._index].sprite;
69908             for (sh = 0, lsh = sprite.length; sh < lsh; sh++) {
69909                 sprite[sh].setAttributes({
69910                     hidden: true
69911                 }, true);
69912             }
69913             if (this.slices[this._index].shadowAttrs) {
69914                 for (i = 0, shadows = this.slices[this._index].shadowAttrs, l = shadows.length; i < l; i++) {
69915                     shadow = shadows[i];
69916                     for (sh = 0, lsh = shadow.length; sh < lsh; sh++) {
69917                         shadow[sh].setAttributes({
69918                             hidden: true
69919                         }, true);
69920                     }
69921                 }
69922             }
69923             this.drawSeries();
69924         }
69925     },
69926     
69927     // @private shows all elements in the series.
69928     showAll: function() {
69929         if (!isNaN(this._index)) {
69930             this.__excludes[this._index] = false;
69931             this.drawSeries();
69932         }
69933     },
69934
69935     /**
69936      * Highlight the specified item. If no item is provided the whole series will be highlighted.
69937      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
69938      */
69939     highlightItem: function(item) {
69940         var me = this,
69941             rad = me.rad;
69942         item = item || this.items[this._index];
69943         
69944         //TODO(nico): sometimes in IE itemmouseover is triggered
69945         //twice without triggering itemmouseout in between. This
69946         //fixes the highlighting bug. Eventually, events should be
69947         //changed to trigger one itemmouseout between two itemmouseovers.
69948         this.unHighlightItem();
69949         
69950         if (!item || item.sprite && item.sprite._animating) {
69951             return;
69952         }
69953         me.callParent([item]);
69954         if (!me.highlight) {
69955             return;
69956         }
69957         if ('segment' in me.highlightCfg) {
69958             var highlightSegment = me.highlightCfg.segment,
69959                 animate = me.chart.animate,
69960                 attrs, i, shadows, shadow, ln, to, itemHighlightSegment, prop;
69961             //animate labels
69962             if (me.labelsGroup) {
69963                 var group = me.labelsGroup,
69964                     display = me.label.display,
69965                     label = group.getAt(item.index),
69966                     middle = (item.startAngle + item.endAngle) / 2 * rad,
69967                     r = highlightSegment.margin || 0,
69968                     x = r * Math.cos(middle),
69969                     y = r * Math.sin(middle);
69970
69971                 //TODO(nico): rounding to 1e-10
69972                 //gives the right translation. Translation
69973                 //was buggy for very small numbers. In this
69974                 //case we're not looking to translate to very small
69975                 //numbers but not to translate at all.
69976                 if (Math.abs(x) < 1e-10) {
69977                     x = 0;
69978                 }
69979                 if (Math.abs(y) < 1e-10) {
69980                     y = 0;
69981                 }
69982                 
69983                 if (animate) {
69984                     label.stopAnimation();
69985                     label.animate({
69986                         to: {
69987                             translate: {
69988                                 x: x,
69989                                 y: y
69990                             }
69991                         },
69992                         duration: me.highlightDuration
69993                     });
69994                 }
69995                 else {
69996                     label.setAttributes({
69997                         translate: {
69998                             x: x,
69999                             y: y
70000                         }
70001                     }, true);
70002                 }
70003             }
70004             //animate shadows
70005             if (me.chart.shadow && item.shadows) {
70006                 i = 0;
70007                 shadows = item.shadows;
70008                 ln = shadows.length;
70009                 for (; i < ln; i++) {
70010                     shadow = shadows[i];
70011                     to = {};
70012                     itemHighlightSegment = item.sprite._from.segment;
70013                     for (prop in itemHighlightSegment) {
70014                         if (! (prop in highlightSegment)) {
70015                             to[prop] = itemHighlightSegment[prop];
70016                         }
70017                     }
70018                     attrs = {
70019                         segment: Ext.applyIf(to, me.highlightCfg.segment)
70020                     };
70021                     if (animate) {
70022                         shadow.stopAnimation();
70023                         shadow.animate({
70024                             to: attrs,
70025                             duration: me.highlightDuration
70026                         });
70027                     }
70028                     else {
70029                         shadow.setAttributes(attrs, true);
70030                     }
70031                 }
70032             }
70033         }
70034     },
70035
70036     /**
70037      * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
70038      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70039      */
70040     unHighlightItem: function() {
70041         var me = this;
70042         if (!me.highlight) {
70043             return;
70044         }
70045
70046         if (('segment' in me.highlightCfg) && me.items) {
70047             var items = me.items,
70048                 animate = me.chart.animate,
70049                 shadowsEnabled = !!me.chart.shadow,
70050                 group = me.labelsGroup,
70051                 len = items.length,
70052                 i = 0,
70053                 j = 0,
70054                 display = me.label.display,
70055                 shadowLen, p, to, ihs, hs, sprite, shadows, shadow, item, label, attrs;
70056
70057             for (; i < len; i++) {
70058                 item = items[i];
70059                 if (!item) {
70060                     continue;
70061                 }
70062                 sprite = item.sprite;
70063                 if (sprite && sprite._highlighted) {
70064                     //animate labels
70065                     if (group) {
70066                         label = group.getAt(item.index);
70067                         attrs = Ext.apply({
70068                             translate: {
70069                                 x: 0,
70070                                 y: 0
70071                             }
70072                         },
70073                         display == 'rotate' ? {
70074                             rotate: {
70075                                 x: label.attr.x,
70076                                 y: label.attr.y,
70077                                 degrees: label.attr.rotation.degrees
70078                             }
70079                         }: {});
70080                         if (animate) {
70081                             label.stopAnimation();
70082                             label.animate({
70083                                 to: attrs,
70084                                 duration: me.highlightDuration
70085                             });
70086                         }
70087                         else {
70088                             label.setAttributes(attrs, true);
70089                         }
70090                     }
70091                     if (shadowsEnabled) {
70092                         shadows = item.shadows;
70093                         shadowLen = shadows.length;
70094                         for (; j < shadowLen; j++) {
70095                             to = {};
70096                             ihs = item.sprite._to.segment;
70097                             hs = item.sprite._from.segment;
70098                             Ext.apply(to, hs);
70099                             for (p in ihs) {
70100                                 if (! (p in hs)) {
70101                                     to[p] = ihs[p];
70102                                 }
70103                             }
70104                             shadow = shadows[j];
70105                             if (animate) {
70106                                 shadow.stopAnimation();
70107                                 shadow.animate({
70108                                     to: {
70109                                         segment: to
70110                                     },
70111                                     duration: me.highlightDuration
70112                                 });
70113                             }
70114                             else {
70115                                 shadow.setAttributes({ segment: to }, true);
70116                             }
70117                         }
70118                     }
70119                 }
70120             }
70121         }
70122         me.callParent(arguments);
70123     },
70124     
70125     /**
70126      * Returns the color of the series (to be displayed as color for the series legend item).
70127      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70128      */
70129     getLegendColor: function(index) {
70130         var me = this;
70131         return me.colorArrayStyle[index % me.colorArrayStyle.length];
70132     }
70133 });
70134
70135
70136 /**
70137  * @class Ext.chart.series.Radar
70138  * @extends Ext.chart.series.Series
70139  * 
70140  * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for 
70141  * a constrained number of categories.
70142  * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart 
70143  * documentation for more information. A typical configuration object for the radar series could be:
70144  * 
70145  * {@img Ext.chart.series.Radar/Ext.chart.series.Radar.png Ext.chart.series.Radar chart series}  
70146  *
70147  *     var store = Ext.create('Ext.data.JsonStore', {
70148  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
70149  *         data: [
70150  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
70151  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
70152  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
70153  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
70154  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
70155  *         ]
70156  *     });
70157  *     
70158  *     Ext.create('Ext.chart.Chart', {
70159  *         renderTo: Ext.getBody(),
70160  *         width: 500,
70161  *         height: 300,
70162  *         animate: true,
70163  *         theme:'Category2',
70164  *         store: store,
70165  *         axes: [{
70166  *             type: 'Radial',
70167  *             position: 'radial',
70168  *             label: {
70169  *                 display: true
70170  *             }
70171  *         }],
70172  *         series: [{
70173  *             type: 'radar',
70174  *             xField: 'name',
70175  *             yField: 'data3',
70176  *             showInLegend: true,
70177  *             showMarkers: true,
70178  *             markerConfig: {
70179  *                 radius: 5,
70180  *                 size: 5           
70181  *             },
70182  *             style: {
70183  *                 'stroke-width': 2,
70184  *                 fill: 'none'
70185  *             }
70186  *         },{
70187  *             type: 'radar',
70188  *             xField: 'name',
70189  *             yField: 'data2',
70190  *             showMarkers: true,
70191  *             showInLegend: true,
70192  *             markerConfig: {
70193  *                 radius: 5,
70194  *                 size: 5
70195  *             },
70196  *             style: {
70197  *                 'stroke-width': 2,
70198  *                 fill: 'none'
70199  *             }
70200  *         },{
70201  *             type: 'radar',
70202  *             xField: 'name',
70203  *             yField: 'data5',
70204  *             showMarkers: true,
70205  *             showInLegend: true,
70206  *             markerConfig: {
70207  *                 radius: 5,
70208  *                 size: 5
70209  *             },
70210  *             style: {
70211  *                 'stroke-width': 2,
70212  *                 fill: 'none'
70213  *             }
70214  *         }]    
70215  *     });
70216  * 
70217  * In this configuration we add three series to the chart. Each of these series is bound to the same categories field, `name` but bound to different properties for each category,
70218  * `data1`, `data2` and `data3` respectively. All series display markers by having `showMarkers` enabled. The configuration for the markers of each series can be set by adding properties onto 
70219  * the markerConfig object. Finally we override some theme styling properties by adding properties to the `style` object.
70220  * 
70221  * @xtype radar
70222  */
70223 Ext.define('Ext.chart.series.Radar', {
70224
70225     /* Begin Definitions */
70226
70227     extend: 'Ext.chart.series.Series',
70228
70229     requires: ['Ext.chart.Shape', 'Ext.fx.Anim'],
70230
70231     /* End Definitions */
70232
70233     type: "radar",
70234     alias: 'series.radar',
70235
70236     
70237     rad: Math.PI / 180,
70238
70239     showInLegend: false,
70240
70241     /**
70242      * @cfg {Object} style
70243      * An object containing styles for overriding series styles from Theming.
70244      */
70245     style: {},
70246     
70247     constructor: function(config) {
70248         this.callParent(arguments);
70249         var me = this,
70250             surface = me.chart.surface, i, l;
70251         me.group = surface.getGroup(me.seriesId);
70252         if (me.showMarkers) {
70253             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
70254         }
70255     },
70256
70257     /**
70258      * Draws the series for the current chart.
70259      */
70260     drawSeries: function() {
70261         var me = this,
70262             store = me.chart.substore || me.chart.store,
70263             group = me.group,
70264             sprite,
70265             chart = me.chart,
70266             animate = chart.animate,
70267             field = me.field || me.yField,
70268             surface = chart.surface,
70269             chartBBox = chart.chartBBox,
70270             rendererAttributes,
70271             centerX, centerY,
70272             items,
70273             radius,
70274             maxValue = 0,
70275             fields = [],
70276             max = Math.max,
70277             cos = Math.cos,
70278             sin = Math.sin,
70279             pi2 = Math.PI * 2,
70280             l = store.getCount(),
70281             startPath, path, x, y, rho,
70282             i, nfields,
70283             seriesStyle = me.seriesStyle,
70284             seriesLabelStyle = me.seriesLabelStyle,
70285             first = chart.resizing || !me.radar,
70286             axis = chart.axes && chart.axes.get(0),
70287             aggregate = !(axis && axis.maximum);
70288         
70289         me.setBBox();
70290
70291         maxValue = aggregate? 0 : (axis.maximum || 0);
70292         
70293         Ext.apply(seriesStyle, me.style || {});
70294         
70295         //if the store is empty then there's nothing to draw
70296         if (!store || !store.getCount()) {
70297             return;
70298         }
70299         
70300         me.unHighlightItem();
70301         me.cleanHighlights();
70302
70303         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
70304         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
70305         me.radius = radius = Math.min(chartBBox.width, chartBBox.height) /2;
70306         me.items = items = [];
70307
70308         if (aggregate) {
70309             //get all renderer fields
70310             chart.series.each(function(series) {
70311                 fields.push(series.yField);
70312             });
70313             //get maxValue to interpolate
70314             store.each(function(record, i) {
70315                 for (i = 0, nfields = fields.length; i < nfields; i++) {
70316                     maxValue = max(+record.get(fields[i]), maxValue);
70317                 }
70318             });
70319         }
70320         //ensure non-zero value.
70321         maxValue = maxValue || 1;
70322         //create path and items
70323         startPath = []; path = [];
70324         store.each(function(record, i) {
70325             rho = radius * record.get(field) / maxValue;
70326             x = rho * cos(i / l * pi2);
70327             y = rho * sin(i / l * pi2);
70328             if (i == 0) {
70329                 path.push('M', x + centerX, y + centerY);
70330                 startPath.push('M', 0.01 * x + centerX, 0.01 * y + centerY);
70331             } else {
70332                 path.push('L', x + centerX, y + centerY);
70333                 startPath.push('L', 0.01 * x + centerX, 0.01 * y + centerY);
70334             }
70335             items.push({
70336                 sprite: false, //TODO(nico): add markers
70337                 point: [centerX + x, centerY + y],
70338                 series: me
70339             });
70340         });
70341         path.push('Z');
70342         //create path sprite
70343         if (!me.radar) {
70344             me.radar = surface.add(Ext.apply({
70345                 type: 'path',
70346                 group: group,
70347                 path: startPath
70348             }, seriesStyle || {}));
70349         }
70350         //reset on resizing
70351         if (chart.resizing) {
70352             me.radar.setAttributes({
70353                 path: startPath
70354             }, true);
70355         }
70356         //render/animate
70357         if (chart.animate) {
70358             me.onAnimate(me.radar, {
70359                 to: Ext.apply({
70360                     path: path
70361                 }, seriesStyle || {})
70362             });
70363         } else {
70364             me.radar.setAttributes(Ext.apply({
70365                 path: path
70366             }, seriesStyle || {}), true);
70367         }
70368         //render markers, labels and callouts
70369         if (me.showMarkers) {
70370             me.drawMarkers();
70371         }
70372         me.renderLabels();
70373         me.renderCallouts();
70374     },
70375     
70376     // @private draws the markers for the lines (if any).
70377     drawMarkers: function() {
70378         var me = this,
70379             chart = me.chart,
70380             surface = chart.surface,
70381             markerStyle = Ext.apply({}, me.markerStyle || {}),
70382             endMarkerStyle = Ext.apply(markerStyle, me.markerConfig),
70383             items = me.items, 
70384             type = endMarkerStyle.type,
70385             markerGroup = me.markerGroup,
70386             centerX = me.centerX,
70387             centerY = me.centerY,
70388             item, i, l, marker;
70389         
70390         delete endMarkerStyle.type;
70391         
70392         for (i = 0, l = items.length; i < l; i++) {
70393             item = items[i];
70394             marker = markerGroup.getAt(i);
70395             if (!marker) {
70396                 marker = Ext.chart.Shape[type](surface, Ext.apply({
70397                     group: markerGroup,
70398                     x: 0,
70399                     y: 0,
70400                     translate: {
70401                         x: centerX,
70402                         y: centerY
70403                     }
70404                 }, endMarkerStyle));
70405             }
70406             else {
70407                 marker.show();
70408             }
70409             if (chart.resizing) {
70410                 marker.setAttributes({
70411                     x: 0,
70412                     y: 0,
70413                     translate: {
70414                         x: centerX,
70415                         y: centerY
70416                     }
70417                 }, true);
70418             }
70419             marker._to = {
70420                 translate: {
70421                     x: item.point[0],
70422                     y: item.point[1]
70423                 }
70424             };
70425             //render/animate
70426             if (chart.animate) {
70427                 me.onAnimate(marker, {
70428                     to: marker._to
70429                 });
70430             }
70431             else {
70432                 marker.setAttributes(Ext.apply(marker._to, endMarkerStyle || {}), true);
70433             }
70434         }
70435     },
70436     
70437     isItemInPoint: function(x, y, item) {
70438         var point,
70439             tolerance = 10,
70440             abs = Math.abs;
70441         point = item.point;
70442         return (abs(point[0] - x) <= tolerance &&
70443                 abs(point[1] - y) <= tolerance);
70444     },
70445
70446     // @private callback for when creating a label sprite.
70447     onCreateLabel: function(storeItem, item, i, display) {
70448         var me = this,
70449             group = me.labelsGroup,
70450             config = me.label,
70451             centerX = me.centerX,
70452             centerY = me.centerY,
70453             point = item.point,
70454             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config);
70455         
70456         return me.chart.surface.add(Ext.apply({
70457             'type': 'text',
70458             'text-anchor': 'middle',
70459             'group': group,
70460             'x': centerX,
70461             'y': centerY
70462         }, config || {}));
70463     },
70464
70465     // @private callback for when placing a label sprite.
70466     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
70467         var me = this,
70468             chart = me.chart,
70469             resizing = chart.resizing,
70470             config = me.label,
70471             format = config.renderer,
70472             field = config.field,
70473             centerX = me.centerX,
70474             centerY = me.centerY,
70475             opt = {
70476                 x: item.point[0],
70477                 y: item.point[1]
70478             },
70479             x = opt.x - centerX,
70480             y = opt.y - centerY;
70481
70482         label.setAttributes({
70483             text: format(storeItem.get(field)),
70484             hidden: true
70485         },
70486         true);
70487         
70488         if (resizing) {
70489             label.setAttributes({
70490                 x: centerX,
70491                 y: centerY
70492             }, true);
70493         }
70494         
70495         if (animate) {
70496             label.show(true);
70497             me.onAnimate(label, {
70498                 to: opt
70499             });
70500         } else {
70501             label.setAttributes(opt, true);
70502             label.show(true);
70503         }
70504     },
70505
70506     // @private for toggling (show/hide) series. 
70507     toggleAll: function(show) {
70508         var me = this,
70509             i, ln, shadow, shadows;
70510         if (!show) {
70511             Ext.chart.series.Radar.superclass.hideAll.call(me);
70512         }
70513         else {
70514             Ext.chart.series.Radar.superclass.showAll.call(me);
70515         }
70516         if (me.radar) {
70517             me.radar.setAttributes({
70518                 hidden: !show
70519             }, true);
70520             //hide shadows too
70521             if (me.radar.shadows) {
70522                 for (i = 0, shadows = me.radar.shadows, ln = shadows.length; i < ln; i++) {
70523                     shadow = shadows[i];
70524                     shadow.setAttributes({
70525                         hidden: !show
70526                     }, true);
70527                 }
70528             }
70529         }
70530     },
70531     
70532     // @private hide all elements in the series.
70533     hideAll: function() {
70534         this.toggleAll(false);
70535         this.hideMarkers(0);
70536     },
70537     
70538     // @private show all elements in the series.
70539     showAll: function() {
70540         this.toggleAll(true);
70541     },
70542     
70543     // @private hide all markers that belong to `markerGroup`
70544     hideMarkers: function(index) {
70545         var me = this,
70546             count = me.markerGroup && me.markerGroup.getCount() || 0,
70547             i = index || 0;
70548         for (; i < count; i++) {
70549             me.markerGroup.getAt(i).hide(true);
70550         }
70551     }
70552 });
70553
70554
70555 /**
70556  * @class Ext.chart.series.Scatter
70557  * @extends Ext.chart.series.Cartesian
70558  * 
70559  * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization. 
70560  * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
70561  * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart 
70562  * documentation for more information on creating charts. A typical configuration object for the scatter could be:
70563  *
70564  * {@img Ext.chart.series.Scatter/Ext.chart.series.Scatter.png Ext.chart.series.Scatter chart series}  
70565  *
70566  *     var store = Ext.create('Ext.data.JsonStore', {
70567  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
70568  *         data: [
70569  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
70570  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
70571  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
70572  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
70573  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}                                                
70574  *         ]
70575  *     });
70576  *     
70577  *     Ext.create('Ext.chart.Chart', {
70578  *         renderTo: Ext.getBody(),
70579  *         width: 500,
70580  *         height: 300,
70581  *         animate: true,
70582  *         theme:'Category2',
70583  *         store: store,
70584  *         axes: [{
70585  *             type: 'Numeric',
70586  *             position: 'bottom',
70587  *             fields: ['data1', 'data2', 'data3'],
70588  *             title: 'Sample Values',
70589  *             grid: true,
70590  *             minimum: 0
70591  *         }, {
70592  *             type: 'Category',
70593  *             position: 'left',
70594  *             fields: ['name'],
70595  *             title: 'Sample Metrics'
70596  *         }],
70597  *         series: [{
70598  *             type: 'scatter',
70599  *             markerConfig: {
70600  *                 radius: 5,
70601  *                 size: 5
70602  *             },
70603  *             axis: 'left',
70604  *             xField: 'name',
70605  *             yField: 'data2'
70606  *         }, {
70607  *             type: 'scatter',
70608  *             markerConfig: {
70609  *                 radius: 5,
70610  *                 size: 5
70611  *             },
70612  *             axis: 'left',
70613  *             xField: 'name',
70614  *             yField: 'data3'
70615  *         }]   
70616  *     });
70617  * 
70618  * 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, 
70619  * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`. 
70620  * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as 
70621  * axis to show the current values of the elements.
70622  * 
70623  * @xtype scatter
70624  */
70625 Ext.define('Ext.chart.series.Scatter', {
70626
70627     /* Begin Definitions */
70628
70629     extend: 'Ext.chart.series.Cartesian',
70630
70631     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
70632
70633     /* End Definitions */
70634
70635     type: 'scatter',
70636     alias: 'series.scatter',
70637
70638     /**
70639      * @cfg {Object} markerConfig
70640      * The display style for the scatter series markers.
70641      */
70642     
70643     /**
70644      * @cfg {Object} style 
70645      * Append styling properties to this object for it to override theme properties.
70646      */
70647
70648     constructor: function(config) {
70649         this.callParent(arguments);
70650         var me = this,
70651             shadow = me.chart.shadow,
70652             surface = me.chart.surface, i, l;
70653         Ext.apply(me, config, {
70654             style: {},
70655             markerConfig: {},
70656             shadowAttributes: [{
70657                 "stroke-width": 6,
70658                 "stroke-opacity": 0.05,
70659                 stroke: 'rgb(0, 0, 0)'
70660             }, {
70661                 "stroke-width": 4,
70662                 "stroke-opacity": 0.1,
70663                 stroke: 'rgb(0, 0, 0)'
70664             }, {
70665                 "stroke-width": 2,
70666                 "stroke-opacity": 0.15,
70667                 stroke: 'rgb(0, 0, 0)'
70668             }]
70669         });
70670         me.group = surface.getGroup(me.seriesId);
70671         if (shadow) {
70672             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
70673                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
70674             }
70675         }
70676     },
70677
70678     // @private Get chart and data boundaries
70679     getBounds: function() {
70680         var me = this,
70681             chart = me.chart,
70682             store = chart.substore || chart.store,
70683             axes = [].concat(me.axis),
70684             bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
70685
70686         me.setBBox();
70687         bbox = me.bbox;
70688
70689         for (i = 0, ln = axes.length; i < ln; i++) { 
70690             axis = chart.axes.get(axes[i]);
70691             if (axis) {
70692                 ends = axis.calcEnds();
70693                 if (axis.position == 'top' || axis.position == 'bottom') {
70694                     minX = ends.from;
70695                     maxX = ends.to;
70696                 }
70697                 else {
70698                     minY = ends.from;
70699                     maxY = ends.to;
70700                 }
70701             }
70702         }
70703         // If a field was specified without a corresponding axis, create one to get bounds
70704         if (me.xField && !Ext.isNumber(minX)) {
70705             axis = Ext.create('Ext.chart.axis.Axis', {
70706                 chart: chart,
70707                 fields: [].concat(me.xField)
70708             }).calcEnds();
70709             minX = axis.from;
70710             maxX = axis.to;
70711         }
70712         if (me.yField && !Ext.isNumber(minY)) {
70713             axis = Ext.create('Ext.chart.axis.Axis', {
70714                 chart: chart,
70715                 fields: [].concat(me.yField)
70716             }).calcEnds();
70717             minY = axis.from;
70718             maxY = axis.to;
70719         }
70720
70721         if (isNaN(minX)) {
70722             minX = 0;
70723             maxX = store.getCount() - 1;
70724             xScale = bbox.width / (store.getCount() - 1);
70725         }
70726         else {
70727             xScale = bbox.width / (maxX - minX);
70728         }
70729
70730         if (isNaN(minY)) {
70731             minY = 0;
70732             maxY = store.getCount() - 1;
70733             yScale = bbox.height / (store.getCount() - 1);
70734         } 
70735         else {
70736             yScale = bbox.height / (maxY - minY);
70737         }
70738
70739         return {
70740             bbox: bbox,
70741             minX: minX,
70742             minY: minY,
70743             xScale: xScale,
70744             yScale: yScale
70745         };
70746     },
70747
70748     // @private Build an array of paths for the chart
70749     getPaths: function() {
70750         var me = this,
70751             chart = me.chart,
70752             enableShadows = chart.shadow,
70753             store = chart.substore || chart.store,
70754             group = me.group,
70755             bounds = me.bounds = me.getBounds(),
70756             bbox = me.bbox,
70757             xScale = bounds.xScale,
70758             yScale = bounds.yScale,
70759             minX = bounds.minX,
70760             minY = bounds.minY,
70761             boxX = bbox.x,
70762             boxY = bbox.y,
70763             boxHeight = bbox.height,
70764             items = me.items = [],
70765             attrs = [],
70766             x, y, xValue, yValue, sprite;
70767
70768         store.each(function(record, i) {
70769             xValue = record.get(me.xField);
70770             yValue = record.get(me.yField);
70771             //skip undefined values
70772             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
70773                 if (Ext.isDefined(Ext.global.console)) {
70774                     Ext.global.console.warn("[Ext.chart.series.Scatter]  Skipping a store element with an undefined value at ", record, xValue, yValue);
70775                 }
70776                 return;
70777             }
70778             // Ensure a value
70779             if (typeof xValue == 'string' || typeof xValue == 'object') {
70780                 xValue = i;
70781             }
70782             if (typeof yValue == 'string' || typeof yValue == 'object') {
70783                 yValue = i;
70784             }
70785             x = boxX + (xValue - minX) * xScale;
70786             y = boxY + boxHeight - (yValue - minY) * yScale;
70787             attrs.push({
70788                 x: x,
70789                 y: y
70790             });
70791
70792             me.items.push({
70793                 series: me,
70794                 value: [xValue, yValue],
70795                 point: [x, y],
70796                 storeItem: record
70797             });
70798
70799             // When resizing, reset before animating
70800             if (chart.animate && chart.resizing) {
70801                 sprite = group.getAt(i);
70802                 if (sprite) {
70803                     me.resetPoint(sprite);
70804                     if (enableShadows) {
70805                         me.resetShadow(sprite);
70806                     }
70807                 }
70808             }
70809         });
70810         return attrs;
70811     },
70812
70813     // @private translate point to the center
70814     resetPoint: function(sprite) {
70815         var bbox = this.bbox;
70816         sprite.setAttributes({
70817             translate: {
70818                 x: (bbox.x + bbox.width) / 2,
70819                 y: (bbox.y + bbox.height) / 2
70820             }
70821         }, true);
70822     },
70823
70824     // @private translate shadows of a sprite to the center
70825     resetShadow: function(sprite) {
70826         var me = this,
70827             shadows = sprite.shadows,
70828             shadowAttributes = me.shadowAttributes,
70829             ln = me.shadowGroups.length,
70830             bbox = me.bbox,
70831             i, attr;
70832         for (i = 0; i < ln; i++) {
70833             attr = Ext.apply({}, shadowAttributes[i]);
70834             if (attr.translate) {
70835                 attr.translate.x += (bbox.x + bbox.width) / 2;
70836                 attr.translate.y += (bbox.y + bbox.height) / 2;
70837             }
70838             else {
70839                 attr.translate = {
70840                     x: (bbox.x + bbox.width) / 2,
70841                     y: (bbox.y + bbox.height) / 2
70842                 };
70843             }
70844             shadows[i].setAttributes(attr, true);
70845         }
70846     },
70847
70848     // @private create a new point
70849     createPoint: function(attr, type) {
70850         var me = this,
70851             chart = me.chart,
70852             group = me.group,
70853             bbox = me.bbox;
70854
70855         return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
70856             x: 0,
70857             y: 0,
70858             group: group,
70859             translate: {
70860                 x: (bbox.x + bbox.width) / 2,
70861                 y: (bbox.y + bbox.height) / 2
70862             }
70863         }, attr));
70864     },
70865
70866     // @private create a new set of shadows for a sprite
70867     createShadow: function(sprite, endMarkerStyle, type) {
70868         var me = this,
70869             chart = me.chart,
70870             shadowGroups = me.shadowGroups,
70871             shadowAttributes = me.shadowAttributes,
70872             lnsh = shadowGroups.length,
70873             bbox = me.bbox,
70874             i, shadow, shadows, attr;
70875
70876         sprite.shadows = shadows = [];
70877
70878         for (i = 0; i < lnsh; i++) {
70879             attr = Ext.apply({}, shadowAttributes[i]);
70880             if (attr.translate) {
70881                 attr.translate.x += (bbox.x + bbox.width) / 2;
70882                 attr.translate.y += (bbox.y + bbox.height) / 2;
70883             }
70884             else {
70885                 Ext.apply(attr, {
70886                     translate: {
70887                         x: (bbox.x + bbox.width) / 2,
70888                         y: (bbox.y + bbox.height) / 2
70889                     }
70890                 });
70891             }
70892             Ext.apply(attr, endMarkerStyle);
70893             shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
70894                 x: 0,
70895                 y: 0,
70896                 group: shadowGroups[i]
70897             }, attr));
70898             shadows.push(shadow);
70899         }
70900     },
70901
70902     /**
70903      * Draws the series for the current chart.
70904      */
70905     drawSeries: function() {
70906         var me = this,
70907             chart = me.chart,
70908             store = chart.substore || chart.store,
70909             group = me.group,
70910             enableShadows = chart.shadow,
70911             shadowGroups = me.shadowGroups,
70912             shadowAttributes = me.shadowAttributes,
70913             lnsh = shadowGroups.length,
70914             sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
70915             rendererAttributes, shadowAttribute;
70916
70917         endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
70918         type = endMarkerStyle.type;
70919         delete endMarkerStyle.type;
70920
70921         //if the store is empty then there's nothing to be rendered
70922         if (!store || !store.getCount()) {
70923             return;
70924         }
70925
70926         me.unHighlightItem();
70927         me.cleanHighlights();
70928
70929         attrs = me.getPaths();
70930         ln = attrs.length;
70931         for (i = 0; i < ln; i++) {
70932             attr = attrs[i];
70933             sprite = group.getAt(i);
70934             Ext.apply(attr, endMarkerStyle);
70935
70936             // Create a new sprite if needed (no height)
70937             if (!sprite) {
70938                 sprite = me.createPoint(attr, type);
70939                 if (enableShadows) {
70940                     me.createShadow(sprite, endMarkerStyle, type);
70941                 }
70942             }
70943
70944             shadows = sprite.shadows;
70945             if (chart.animate) {
70946                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
70947                 sprite._to = rendererAttributes;
70948                 me.onAnimate(sprite, {
70949                     to: rendererAttributes
70950                 });
70951                 //animate shadows
70952                 for (shindex = 0; shindex < lnsh; shindex++) {
70953                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
70954                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
70955                         translate: {
70956                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
70957                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
70958                         } 
70959                     }, shadowAttribute), i, store);
70960                     me.onAnimate(shadows[shindex], { to: rendererAttributes });
70961                 }
70962             }
70963             else {
70964                 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply({ translate: attr }, { hidden: false }), i, store);
70965                 sprite.setAttributes(rendererAttributes, true);
70966                 //update shadows
70967                 for (shindex = 0; shindex < lnsh; shindex++) {
70968                     shadowAttribute = shadowAttributes[shindex];
70969                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({ 
70970                         x: attr.x,
70971                         y: attr.y
70972                     }, shadowAttribute), i, store);
70973                     shadows[shindex].setAttributes(rendererAttributes, true);
70974                 }
70975             }
70976             me.items[i].sprite = sprite;
70977         }
70978
70979         // Hide unused sprites
70980         ln = group.getCount();
70981         for (i = attrs.length; i < ln; i++) {
70982             group.getAt(i).hide(true);
70983         }
70984         me.renderLabels();
70985         me.renderCallouts();
70986     },
70987     
70988     // @private callback for when creating a label sprite.
70989     onCreateLabel: function(storeItem, item, i, display) {
70990         var me = this,
70991             group = me.labelsGroup,
70992             config = me.label,
70993             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
70994             bbox = me.bbox;
70995         
70996         return me.chart.surface.add(Ext.apply({
70997             type: 'text',
70998             group: group,
70999             x: item.point[0],
71000             y: bbox.y + bbox.height / 2
71001         }, endLabelStyle));
71002     },
71003     
71004     // @private callback for when placing a label sprite.
71005     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
71006         var me = this,
71007             chart = me.chart,
71008             resizing = chart.resizing,
71009             config = me.label,
71010             format = config.renderer,
71011             field = config.field,
71012             bbox = me.bbox,
71013             x = item.point[0],
71014             y = item.point[1],
71015             radius = item.sprite.attr.radius,
71016             bb, width, height, anim;
71017         
71018         label.setAttributes({
71019             text: format(storeItem.get(field)),
71020             hidden: true
71021         }, true);
71022         
71023         if (display == 'rotate') {
71024             label.setAttributes({
71025                 'text-anchor': 'start',
71026                 'rotation': {
71027                     x: x,
71028                     y: y,
71029                     degrees: -45
71030                 }
71031             }, true);
71032             //correct label position to fit into the box
71033             bb = label.getBBox();
71034             width = bb.width;
71035             height = bb.height;
71036             x = x < bbox.x? bbox.x : x;
71037             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
71038             y = (y - height < bbox.y)? bbox.y + height : y;
71039         
71040         } else if (display == 'under' || display == 'over') {
71041             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
71042             bb = item.sprite.getBBox();
71043             bb.width = bb.width || (radius * 2);
71044             bb.height = bb.height || (radius * 2);
71045             y = y + (display == 'over'? -bb.height : bb.height);
71046             //correct label position to fit into the box
71047             bb = label.getBBox();
71048             width = bb.width/2;
71049             height = bb.height/2;
71050             x = x - width < bbox.x ? bbox.x + width : x;
71051             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
71052             y = y - height < bbox.y? bbox.y + height : y;
71053             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
71054         }
71055
71056         if (!chart.animate) {
71057             label.setAttributes({
71058                 x: x,
71059                 y: y
71060             }, true);
71061             label.show(true);
71062         }
71063         else {
71064             if (resizing) {
71065                 anim = item.sprite.getActiveAnimation();
71066                 if (anim) {
71067                     anim.on('afteranimate', function() {
71068                         label.setAttributes({
71069                             x: x,
71070                             y: y
71071                         }, true);
71072                         label.show(true);
71073                     });   
71074                 }
71075                 else {
71076                     label.show(true);
71077                 }
71078             }
71079             else {
71080                 me.onAnimate(label, {
71081                     to: {
71082                         x: x,
71083                         y: y
71084                     }
71085                 });
71086             }
71087         }
71088     },
71089     
71090     // @private callback for when placing a callout sprite.    
71091     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
71092         var me = this,
71093             chart = me.chart,
71094             surface = chart.surface,
71095             resizing = chart.resizing,
71096             config = me.callouts,
71097             items = me.items,
71098             cur = item.point,
71099             normal,
71100             bbox = callout.label.getBBox(),
71101             offsetFromViz = 30,
71102             offsetToSide = 10,
71103             offsetBox = 3,
71104             boxx, boxy, boxw, boxh,
71105             p, clipRect = me.bbox,
71106             x, y;
71107     
71108         //position
71109         normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
71110         x = cur[0] + normal[0] * offsetFromViz;
71111         y = cur[1] + normal[1] * offsetFromViz;
71112         
71113         //box position and dimensions
71114         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
71115         boxy = y - bbox.height /2 - offsetBox;
71116         boxw = bbox.width + 2 * offsetBox;
71117         boxh = bbox.height + 2 * offsetBox;
71118         
71119         //now check if we're out of bounds and invert the normal vector correspondingly
71120         //this may add new overlaps between labels (but labels won't be out of bounds).
71121         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
71122             normal[0] *= -1;
71123         }
71124         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
71125             normal[1] *= -1;
71126         }
71127     
71128         //update positions
71129         x = cur[0] + normal[0] * offsetFromViz;
71130         y = cur[1] + normal[1] * offsetFromViz;
71131         
71132         //update box position and dimensions
71133         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
71134         boxy = y - bbox.height /2 - offsetBox;
71135         boxw = bbox.width + 2 * offsetBox;
71136         boxh = bbox.height + 2 * offsetBox;
71137         
71138         if (chart.animate) {
71139             //set the line from the middle of the pie to the box.
71140             me.onAnimate(callout.lines, {
71141                 to: {
71142                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
71143                 }
71144             }, true);
71145             //set box position
71146             me.onAnimate(callout.box, {
71147                 to: {
71148                     x: boxx,
71149                     y: boxy,
71150                     width: boxw,
71151                     height: boxh
71152                 }
71153             }, true);
71154             //set text position
71155             me.onAnimate(callout.label, {
71156                 to: {
71157                     x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
71158                     y: y
71159                 }
71160             }, true);
71161         } else {
71162             //set the line from the middle of the pie to the box.
71163             callout.lines.setAttributes({
71164                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
71165             }, true);
71166             //set box position
71167             callout.box.setAttributes({
71168                 x: boxx,
71169                 y: boxy,
71170                 width: boxw,
71171                 height: boxh
71172             }, true);
71173             //set text position
71174             callout.label.setAttributes({
71175                 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
71176                 y: y
71177             }, true);
71178         }
71179         for (p in callout) {
71180             callout[p].show(true);
71181         }
71182     },
71183
71184     // @private handles sprite animation for the series.
71185     onAnimate: function(sprite, attr) {
71186         sprite.show();
71187         return this.callParent(arguments);
71188     },
71189
71190     isItemInPoint: function(x, y, item) {
71191         var point,
71192             tolerance = 10,
71193             abs = Math.abs;
71194
71195         function dist(point) {
71196             var dx = abs(point[0] - x),
71197                 dy = abs(point[1] - y);
71198             return Math.sqrt(dx * dx + dy * dy);
71199         }
71200         point = item.point;
71201         return (point[0] - tolerance <= x && point[0] + tolerance >= x &&
71202             point[1] - tolerance <= y && point[1] + tolerance >= y);
71203     }
71204 });
71205
71206
71207 /**
71208  * @class Ext.chart.theme.Base
71209  * Provides default colors for non-specified things. Should be sub-classed when creating new themes.
71210  * @ignore
71211  */
71212 Ext.define('Ext.chart.theme.Base', {
71213
71214     /* Begin Definitions */
71215
71216     requires: ['Ext.chart.theme.Theme'],
71217
71218     /* End Definitions */
71219
71220     constructor: function(config) {
71221         Ext.chart.theme.call(this, config, {
71222             background: false,
71223             axis: {
71224                 stroke: '#444',
71225                 'stroke-width': 1
71226             },
71227             axisLabelTop: {
71228                 fill: '#444',
71229                 font: '12px Arial, Helvetica, sans-serif',
71230                 spacing: 2,
71231                 padding: 5,
71232                 renderer: function(v) { return v; }
71233             },
71234             axisLabelRight: {
71235                 fill: '#444',
71236                 font: '12px Arial, Helvetica, sans-serif',
71237                 spacing: 2,
71238                 padding: 5,
71239                 renderer: function(v) { return v; }
71240             },
71241             axisLabelBottom: {
71242                 fill: '#444',
71243                 font: '12px Arial, Helvetica, sans-serif',
71244                 spacing: 2,
71245                 padding: 5,
71246                 renderer: function(v) { return v; }
71247             },
71248             axisLabelLeft: {
71249                 fill: '#444',
71250                 font: '12px Arial, Helvetica, sans-serif',
71251                 spacing: 2,
71252                 padding: 5,
71253                 renderer: function(v) { return v; }
71254             },
71255             axisTitleTop: {
71256                 font: 'bold 18px Arial',
71257                 fill: '#444'
71258             },
71259             axisTitleRight: {
71260                 font: 'bold 18px Arial',
71261                 fill: '#444',
71262                 rotate: {
71263                     x:0, y:0,
71264                     degrees: 270
71265                 }
71266             },
71267             axisTitleBottom: {
71268                 font: 'bold 18px Arial',
71269                 fill: '#444'
71270             },
71271             axisTitleLeft: {
71272                 font: 'bold 18px Arial',
71273                 fill: '#444',
71274                 rotate: {
71275                     x:0, y:0,
71276                     degrees: 270
71277                 }
71278             },
71279             series: {
71280                 'stroke-width': 0
71281             },
71282             seriesLabel: {
71283                 font: '12px Arial',
71284                 fill: '#333'
71285             },
71286             marker: {
71287                 stroke: '#555',
71288                 fill: '#000',
71289                 radius: 3,
71290                 size: 3
71291             },
71292             colors: [ "#94ae0a", "#115fa6","#a61120", "#ff8809", "#ffd13e", "#a61187", "#24ad9a", "#7c7474", "#a66111"],
71293             seriesThemes: [{
71294                 fill: "#115fa6"
71295             }, {
71296                 fill: "#94ae0a"
71297             }, {
71298                 fill: "#a61120"
71299             }, {
71300                 fill: "#ff8809"
71301             }, {
71302                 fill: "#ffd13e"
71303             }, {
71304                 fill: "#a61187"
71305             }, {
71306                 fill: "#24ad9a"
71307             }, {
71308                 fill: "#7c7474"
71309             }, {
71310                 fill: "#a66111"
71311             }],
71312             markerThemes: [{
71313                 fill: "#115fa6",
71314                 type: 'circle' 
71315             }, {
71316                 fill: "#94ae0a",
71317                 type: 'cross'
71318             }, {
71319                 fill: "#a61120",
71320                 type: 'plus'
71321             }]
71322         });
71323     }
71324 }, function() {
71325     var palette = ['#b1da5a', '#4ce0e7', '#e84b67', '#da5abd', '#4d7fe6', '#fec935'],
71326         names = ['Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow'],
71327         i = 0, j = 0, l = palette.length, themes = Ext.chart.theme,
71328         categories = [['#f0a50a', '#c20024', '#2044ba', '#810065', '#7eae29'],
71329                       ['#6d9824', '#87146e', '#2a9196', '#d39006', '#1e40ac'],
71330                       ['#fbbc29', '#ce2e4e', '#7e0062', '#158b90', '#57880e'],
71331                       ['#ef5773', '#fcbd2a', '#4f770d', '#1d3eaa', '#9b001f'],
71332                       ['#7eae29', '#fdbe2a', '#910019', '#27b4bc', '#d74dbc'],
71333                       ['#44dce1', '#0b2592', '#996e05', '#7fb325', '#b821a1']],
71334         cats = categories.length;
71335     
71336     //Create themes from base colors
71337     for (; i < l; i++) {
71338         themes[names[i]] = (function(color) {
71339             return Ext.extend(themes.Base, {
71340                 constructor: function(config) {
71341                     themes.Base.prototype.constructor.call(this, Ext.apply({
71342                         baseColor: color
71343                     }, config));
71344                 }
71345             });
71346         })(palette[i]);
71347     }
71348     
71349     //Create theme from color array
71350     for (i = 0; i < cats; i++) {
71351         themes['Category' + (i + 1)] = (function(category) {
71352             return Ext.extend(themes.Base, {
71353                 constructor: function(config) {
71354                     themes.Base.prototype.constructor.call(this, Ext.apply({
71355                         colors: category
71356                     }, config));
71357                 }
71358             });
71359         })(categories[i]);
71360     }
71361 });
71362
71363 /**
71364  * @author Ed Spencer
71365  * @class Ext.data.ArrayStore
71366  * @extends Ext.data.Store
71367  * @ignore
71368  *
71369  * <p>Small helper class to make creating {@link Ext.data.Store}s from Array data easier.
71370  * An ArrayStore will be automatically configured with a {@link Ext.data.reader.Array}.</p>
71371  *
71372  * <p>A store configuration would be something like:</p>
71373 <pre><code>
71374 var store = new Ext.data.ArrayStore({
71375     // store configs
71376     autoDestroy: true,
71377     storeId: 'myStore',
71378     // reader configs
71379     idIndex: 0,
71380     fields: [
71381        'company',
71382        {name: 'price', type: 'float'},
71383        {name: 'change', type: 'float'},
71384        {name: 'pctChange', type: 'float'},
71385        {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
71386     ]
71387 });
71388 </code></pre>
71389  * <p>This store is configured to consume a returned object of the form:
71390 <pre><code>
71391 var myData = [
71392     ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
71393     ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
71394     ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
71395     ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
71396     ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
71397 ];
71398 </code></pre>
71399 *
71400  * <p>An object literal of this form could also be used as the {@link #data} config option.</p>
71401  *
71402  * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
71403  * <b>{@link Ext.data.reader.Array ArrayReader}</b>.</p>
71404  *
71405  * @constructor
71406  * @param {Object} config
71407  * @xtype arraystore
71408  */
71409 Ext.define('Ext.data.ArrayStore', {
71410     extend: 'Ext.data.Store',
71411     alias: 'store.array',
71412     uses: ['Ext.data.reader.Array'],
71413
71414     /**
71415      * @cfg {Ext.data.DataReader} reader @hide
71416      */
71417     constructor: function(config) {
71418         config = config || {};
71419
71420         Ext.applyIf(config, {
71421             proxy: {
71422                 type: 'memory',
71423                 reader: 'array'
71424             }
71425         });
71426
71427         this.callParent([config]);
71428     },
71429
71430     loadData: function(data, append) {
71431         if (this.expandData === true) {
71432             var r = [],
71433                 i = 0,
71434                 ln = data.length;
71435
71436             for (; i < ln; i++) {
71437                 r[r.length] = [data[i]];
71438             }
71439
71440             data = r;
71441         }
71442
71443         this.callParent([data, append]);
71444     }
71445 }, function() {
71446     // backwards compat
71447     Ext.data.SimpleStore = Ext.data.ArrayStore;
71448     // Ext.reg('simplestore', Ext.data.SimpleStore);
71449 });
71450
71451 /**
71452  * @author Ed Spencer
71453  * @class Ext.data.Batch
71454  * 
71455  * <p>Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the 'operationcomplete' event
71456  * after the completion of each Operation, and the 'complete' event when all Operations have been successfully executed. Fires an 'exception'
71457  * event if any of the Operations encounter an exception.</p>
71458  * 
71459  * <p>Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes</p>
71460  * 
71461  * @constructor
71462  * @param {Object} config Optional config object
71463  */
71464 Ext.define('Ext.data.Batch', {
71465     mixins: {
71466         observable: 'Ext.util.Observable'
71467     },
71468     
71469     /**
71470      * True to immediately start processing the batch as soon as it is constructed (defaults to false)
71471      * @property autoStart
71472      * @type Boolean
71473      */
71474     autoStart: false,
71475     
71476     /**
71477      * The index of the current operation being executed
71478      * @property current
71479      * @type Number
71480      */
71481     current: -1,
71482     
71483     /**
71484      * The total number of operations in this batch. Read only
71485      * @property total
71486      * @type Number
71487      */
71488     total: 0,
71489     
71490     /**
71491      * True if the batch is currently running
71492      * @property isRunning
71493      * @type Boolean
71494      */
71495     isRunning: false,
71496     
71497     /**
71498      * True if this batch has been executed completely
71499      * @property isComplete
71500      * @type Boolean
71501      */
71502     isComplete: false,
71503     
71504     /**
71505      * True if this batch has encountered an exception. This is cleared at the start of each operation
71506      * @property hasException
71507      * @type Boolean
71508      */
71509     hasException: false,
71510     
71511     /**
71512      * True to automatically pause the execution of the batch if any operation encounters an exception (defaults to true)
71513      * @property pauseOnException
71514      * @type Boolean
71515      */
71516     pauseOnException: true,
71517     
71518     constructor: function(config) {   
71519         var me = this;
71520                      
71521         me.addEvents(
71522           /**
71523            * @event complete
71524            * Fired when all operations of this batch have been completed
71525            * @param {Ext.data.Batch} batch The batch object
71526            * @param {Object} operation The last operation that was executed
71527            */
71528           'complete',
71529           
71530           /**
71531            * @event exception
71532            * Fired when a operation encountered an exception
71533            * @param {Ext.data.Batch} batch The batch object
71534            * @param {Object} operation The operation that encountered the exception
71535            */
71536           'exception',
71537           
71538           /**
71539            * @event operationcomplete
71540            * Fired when each operation of the batch completes
71541            * @param {Ext.data.Batch} batch The batch object
71542            * @param {Object} operation The operation that just completed
71543            */
71544           'operationcomplete'
71545         );
71546         
71547         me.mixins.observable.constructor.call(me, config);
71548         
71549         /**
71550          * Ordered array of operations that will be executed by this batch
71551          * @property operations
71552          * @type Array
71553          */
71554         me.operations = [];
71555     },
71556     
71557     /**
71558      * Adds a new operation to this batch
71559      * @param {Object} operation The {@link Ext.data.Operation Operation} object
71560      */
71561     add: function(operation) {
71562         this.total++;
71563         
71564         operation.setBatch(this);
71565         
71566         this.operations.push(operation);
71567     },
71568     
71569     /**
71570      * Kicks off the execution of the batch, continuing from the next operation if the previous
71571      * operation encountered an exception, or if execution was paused
71572      */
71573     start: function() {
71574         this.hasException = false;
71575         this.isRunning = true;
71576         
71577         this.runNextOperation();
71578     },
71579     
71580     /**
71581      * @private
71582      * Runs the next operation, relative to this.current.
71583      */
71584     runNextOperation: function() {
71585         this.runOperation(this.current + 1);
71586     },
71587     
71588     /**
71589      * Pauses execution of the batch, but does not cancel the current operation
71590      */
71591     pause: function() {
71592         this.isRunning = false;
71593     },
71594     
71595     /**
71596      * Executes a operation by its numeric index
71597      * @param {Number} index The operation index to run
71598      */
71599     runOperation: function(index) {
71600         var me = this,
71601             operations = me.operations,
71602             operation  = operations[index],
71603             onProxyReturn;
71604         
71605         if (operation === undefined) {
71606             me.isRunning  = false;
71607             me.isComplete = true;
71608             me.fireEvent('complete', me, operations[operations.length - 1]);
71609         } else {
71610             me.current = index;
71611             
71612             onProxyReturn = function(operation) {
71613                 var hasException = operation.hasException();
71614                 
71615                 if (hasException) {
71616                     me.hasException = true;
71617                     me.fireEvent('exception', me, operation);
71618                 } else {
71619                     me.fireEvent('operationcomplete', me, operation);
71620                 }
71621
71622                 if (hasException && me.pauseOnException) {
71623                     me.pause();
71624                 } else {
71625                     operation.setCompleted();
71626                     me.runNextOperation();
71627                 }
71628             };
71629             
71630             operation.setStarted();
71631             
71632             me.proxy[operation.action](operation, onProxyReturn, me);
71633         }
71634     }
71635 });
71636 /**
71637  * @author Ed Spencer
71638  * @class Ext.data.BelongsToAssociation
71639  * @extends Ext.data.Association
71640  *
71641  * <p>Represents a many to one association with another model. The owner model is expected to have
71642  * a foreign key which references the primary key of the associated model:</p>
71643  *
71644 <pre><code>
71645 Ext.define('Category', {
71646     extend: 'Ext.data.Model',
71647     fields: [
71648         {name: 'id',   type: 'int'},
71649         {name: 'name', type: 'string'}
71650     ]
71651 });
71652
71653 Ext.define('Product', {
71654     extend: 'Ext.data.Model',
71655     fields: [
71656         {name: 'id',          type: 'int'},
71657         {name: 'category_id', type: 'int'},
71658         {name: 'name',        type: 'string'}
71659     ],
71660     // we can use the belongsTo shortcut on the model to create a belongsTo association
71661     belongsTo: {type: 'belongsTo', model: 'Category'}
71662 });
71663 </code></pre>
71664  * <p>In the example above we have created models for Products and Categories, and linked them together
71665  * by saying that each Product belongs to a Category. This automatically links each Product to a Category
71666  * based on the Product's category_id, and provides new functions on the Product model:</p>
71667  *
71668  * <p><u>Generated getter function</u></p>
71669  *
71670  * <p>The first function that is added to the owner model is a getter function:</p>
71671  *
71672 <pre><code>
71673 var product = new Product({
71674     id: 100,
71675     category_id: 20,
71676     name: 'Sneakers'
71677 });
71678
71679 product.getCategory(function(category, operation) {
71680     //do something with the category object
71681     alert(category.get('id')); //alerts 20
71682 }, this);
71683 </code></pre>
71684 *
71685  * <p>The getCategory function was created on the Product model when we defined the association. This uses the
71686  * Category's configured {@link Ext.data.proxy.Proxy proxy} to load the Category asynchronously, calling the provided
71687  * callback when it has loaded.</p>
71688  *
71689  * <p>The new getCategory function will also accept an object containing success, failure and callback properties
71690  * - callback will always be called, success will only be called if the associated model was loaded successfully
71691  * and failure will only be called if the associatied model could not be loaded:</p>
71692  *
71693 <pre><code>
71694 product.getCategory({
71695     callback: function(category, operation) {}, //a function that will always be called
71696     success : function(category, operation) {}, //a function that will only be called if the load succeeded
71697     failure : function(category, operation) {}, //a function that will only be called if the load did not succeed
71698     scope   : this //optionally pass in a scope object to execute the callbacks in
71699 });
71700 </code></pre>
71701  *
71702  * <p>In each case above the callbacks are called with two arguments - the associated model instance and the
71703  * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
71704  * useful when the instance could not be loaded.</p>
71705  *
71706  * <p><u>Generated setter function</u></p>
71707  *
71708  * <p>The second generated function sets the associated model instance - if only a single argument is passed to
71709  * the setter then the following two calls are identical:</p>
71710  *
71711 <pre><code>
71712 //this call
71713 product.setCategory(10);
71714
71715 //is equivalent to this call:
71716 product.set('category_id', 10);
71717 </code></pre>
71718  * <p>If we pass in a second argument, the model will be automatically saved and the second argument passed to
71719  * the owner model's {@link Ext.data.Model#save save} method:</p>
71720 <pre><code>
71721 product.setCategory(10, function(product, operation) {
71722     //the product has been saved
71723     alert(product.get('category_id')); //now alerts 10
71724 });
71725
71726 //alternative syntax:
71727 product.setCategory(10, {
71728     callback: function(product, operation), //a function that will always be called
71729     success : function(product, operation), //a function that will only be called if the load succeeded
71730     failure : function(product, operation), //a function that will only be called if the load did not succeed
71731     scope   : this //optionally pass in a scope object to execute the callbacks in
71732 })
71733 </code></pre>
71734 *
71735  * <p><u>Customisation</u></p>
71736  *
71737  * <p>Associations reflect on the models they are linking to automatically set up properties such as the
71738  * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:</p>
71739  *
71740 <pre><code>
71741 Ext.define('Product', {
71742     fields: [...],
71743
71744     associations: [
71745         {type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id'}
71746     ]
71747 });
71748  </code></pre>
71749  *
71750  * <p>Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
71751  * with our own settings. Usually this will not be needed.</p>
71752  */
71753 Ext.define('Ext.data.BelongsToAssociation', {
71754     extend: 'Ext.data.Association',
71755
71756     alias: 'association.belongsto',
71757
71758     /**
71759      * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
71760      * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
71761      * model called Product would set up a product_id foreign key.
71762      * <pre><code>
71763 Ext.define('Order', {
71764     extend: 'Ext.data.Model',
71765     fields: ['id', 'date'],
71766     hasMany: 'Product'
71767 });
71768
71769 Ext.define('Product', {
71770     extend: 'Ext.data.Model',
71771     fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
71772     belongsTo: 'Group'
71773 });
71774 var product = new Product({
71775     id: 1,
71776     name: 'Product 1',
71777     order_id: 22
71778 }, 1);
71779 product.getOrder(); // Will make a call to the server asking for order_id 22
71780
71781      * </code></pre>
71782      */
71783
71784     /**
71785      * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
71786      * Defaults to 'get' + the name of the foreign model, e.g. getCategory
71787      */
71788
71789     /**
71790      * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
71791      * Defaults to 'set' + the name of the foreign model, e.g. setCategory
71792      */
71793     
71794     /**
71795      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
71796      * Use 'belongsTo' to create a HasManyAssocation
71797      * <pre><code>
71798 associations: [{
71799     type: 'belongsTo',
71800     model: 'User'
71801 }]
71802      * </code></pre>
71803      */
71804
71805     constructor: function(config) {
71806         this.callParent(arguments);
71807
71808         var me             = this,
71809             ownerProto     = me.ownerModel.prototype,
71810             associatedName = me.associatedName,
71811             getterName     = me.getterName || 'get' + associatedName,
71812             setterName     = me.setterName || 'set' + associatedName;
71813
71814         Ext.applyIf(me, {
71815             name        : associatedName,
71816             foreignKey  : associatedName.toLowerCase() + "_id",
71817             instanceName: associatedName + 'BelongsToInstance',
71818             associationKey: associatedName.toLowerCase()
71819         });
71820
71821         ownerProto[getterName] = me.createGetter();
71822         ownerProto[setterName] = me.createSetter();
71823     },
71824
71825     /**
71826      * @private
71827      * Returns a setter function to be placed on the owner model's prototype
71828      * @return {Function} The setter function
71829      */
71830     createSetter: function() {
71831         var me              = this,
71832             ownerModel      = me.ownerModel,
71833             associatedModel = me.associatedModel,
71834             foreignKey      = me.foreignKey,
71835             primaryKey      = me.primaryKey;
71836
71837         //'this' refers to the Model instance inside this function
71838         return function(value, options, scope) {
71839             this.set(foreignKey, value);
71840
71841             if (typeof options == 'function') {
71842                 options = {
71843                     callback: options,
71844                     scope: scope || this
71845                 };
71846             }
71847
71848             if (Ext.isObject(options)) {
71849                 return this.save(options);
71850             }
71851         };
71852     },
71853
71854     /**
71855      * @private
71856      * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
71857      * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
71858      * @return {Function} The getter function
71859      */
71860     createGetter: function() {
71861         var me              = this,
71862             ownerModel      = me.ownerModel,
71863             associatedName  = me.associatedName,
71864             associatedModel = me.associatedModel,
71865             foreignKey      = me.foreignKey,
71866             primaryKey      = me.primaryKey,
71867             instanceName    = me.instanceName;
71868
71869         //'this' refers to the Model instance inside this function
71870         return function(options, scope) {
71871             options = options || {};
71872
71873             var foreignKeyId = this.get(foreignKey),
71874                 instance, callbackFn;
71875
71876             if (this[instanceName] === undefined) {
71877                 instance = Ext.ModelManager.create({}, associatedName);
71878                 instance.set(primaryKey, foreignKeyId);
71879
71880                 if (typeof options == 'function') {
71881                     options = {
71882                         callback: options,
71883                         scope: scope || this
71884                     };
71885                 }
71886
71887                 associatedModel.load(foreignKeyId, options);
71888             } else {
71889                 instance = this[instanceName];
71890
71891                 //TODO: We're duplicating the callback invokation code that the instance.load() call above
71892                 //makes here - ought to be able to normalize this - perhaps by caching at the Model.load layer
71893                 //instead of the association layer.
71894                 if (typeof options == 'function') {
71895                     options.call(scope || this, instance);
71896                 }
71897
71898                 if (options.success) {
71899                     options.success.call(scope || this, instance);
71900                 }
71901
71902                 if (options.callback) {
71903                     options.callback.call(scope || this, instance);
71904                 }
71905
71906                 return instance;
71907             }
71908         };
71909     },
71910
71911     /**
71912      * Read associated data
71913      * @private
71914      * @param {Ext.data.Model} record The record we're writing to
71915      * @param {Ext.data.reader.Reader} reader The reader for the associated model
71916      * @param {Object} associationData The raw associated data
71917      */
71918     read: function(record, reader, associationData){
71919         record[this.instanceName] = reader.read([associationData]).records[0];
71920     }
71921 });
71922
71923 /**
71924  * @class Ext.data.BufferStore
71925  * @extends Ext.data.Store
71926  * @ignore
71927  */
71928 Ext.define('Ext.data.BufferStore', {
71929     extend: 'Ext.data.Store',
71930     alias: 'store.buffer',
71931     sortOnLoad: false,
71932     filterOnLoad: false,
71933     
71934     constructor: function() {
71935         Ext.Error.raise('The BufferStore class has been deprecated. Instead, specify the buffered config option on Ext.data.Store');
71936     }
71937 });
71938 /**
71939  * @class Ext.direct.Manager
71940  * <p><b><u>Overview</u></b></p>
71941  *
71942  * <p>Ext.Direct aims to streamline communication between the client and server
71943  * by providing a single interface that reduces the amount of common code
71944  * typically required to validate data and handle returned data packets
71945  * (reading data, error conditions, etc).</p>
71946  *
71947  * <p>The Ext.direct namespace includes several classes for a closer integration
71948  * with the server-side. The Ext.data namespace also includes classes for working
71949  * with Ext.data.Stores which are backed by data from an Ext.Direct method.</p>
71950  *
71951  * <p><b><u>Specification</u></b></p>
71952  *
71953  * <p>For additional information consult the
71954  * <a href="http://sencha.com/products/extjs/extdirect">Ext.Direct Specification</a>.</p>
71955  *
71956  * <p><b><u>Providers</u></b></p>
71957  *
71958  * <p>Ext.Direct uses a provider architecture, where one or more providers are
71959  * used to transport data to and from the server. There are several providers
71960  * that exist in the core at the moment:</p><div class="mdetail-params"><ul>
71961  *
71962  * <li>{@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations</li>
71963  * <li>{@link Ext.direct.PollingProvider PollingProvider} for repeated requests</li>
71964  * <li>{@link Ext.direct.RemotingProvider RemotingProvider} exposes server side
71965  * on the client.</li>
71966  * </ul></div>
71967  *
71968  * <p>A provider does not need to be invoked directly, providers are added via
71969  * {@link Ext.direct.Manager}.{@link Ext.direct.Manager#add add}.</p>
71970  *
71971  * <p><b><u>Router</u></b></p>
71972  *
71973  * <p>Ext.Direct utilizes a "router" on the server to direct requests from the client
71974  * to the appropriate server-side method. Because the Ext.Direct API is completely
71975  * platform-agnostic, you could completely swap out a Java based server solution
71976  * and replace it with one that uses C# without changing the client side JavaScript
71977  * at all.</p>
71978  *
71979  * <p><b><u>Server side events</u></b></p>
71980  *
71981  * <p>Custom events from the server may be handled by the client by adding
71982  * listeners, for example:</p>
71983  * <pre><code>
71984 {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
71985
71986 // add a handler for a 'message' event sent by the server
71987 Ext.direct.Manager.on('message', function(e){
71988     out.append(String.format('&lt;p>&lt;i>{0}&lt;/i>&lt;/p>', e.data));
71989             out.el.scrollTo('t', 100000, true);
71990 });
71991  * </code></pre>
71992  * @singleton
71993  */
71994
71995 Ext.define('Ext.direct.Manager', {
71996     
71997     /* Begin Definitions */
71998     singleton: true,
71999    
72000     mixins: {
72001         observable: 'Ext.util.Observable'
72002     },
72003     
72004     requires: ['Ext.util.MixedCollection'],
72005     
72006     statics: {
72007         exceptions: {
72008             TRANSPORT: 'xhr',
72009             PARSE: 'parse',
72010             LOGIN: 'login',
72011             SERVER: 'exception'
72012         }
72013     },
72014     
72015     /* End Definitions */
72016    
72017     constructor: function(){
72018         var me = this;
72019        
72020         me.addEvents(
72021             /**
72022              * @event event
72023              * Fires after an event.
72024              * @param {event} e The Ext.direct.Event type that occurred.
72025              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
72026              */
72027             'event',
72028             /**
72029              * @event exception
72030              * Fires after an event exception.
72031              * @param {event} e The Ext.direct.Event type that occurred.
72032              */
72033             'exception'
72034         );
72035         me.transactions = Ext.create('Ext.util.MixedCollection');
72036         me.providers = Ext.create('Ext.util.MixedCollection');
72037         
72038         me.mixins.observable.constructor.call(me);
72039     },
72040     
72041     /**
72042      * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods.
72043      * If the provider is not already connected, it will auto-connect.
72044      * <pre><code>
72045 var pollProv = new Ext.direct.PollingProvider({
72046     url: 'php/poll2.php'
72047 });
72048
72049 Ext.direct.Manager.addProvider({
72050     "type":"remoting",       // create a {@link Ext.direct.RemotingProvider}
72051     "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
72052     "actions":{              // each property within the actions object represents a Class
72053         "TestAction":[       // array of methods within each server side Class
72054         {
72055             "name":"doEcho", // name of method
72056             "len":1
72057         },{
72058             "name":"multiply",
72059             "len":1
72060         },{
72061             "name":"doForm",
72062             "formHandler":true, // handle form on server with Ext.Direct.Transaction
72063             "len":1
72064         }]
72065     },
72066     "namespace":"myApplication",// namespace to create the Remoting Provider in
72067 },{
72068     type: 'polling', // create a {@link Ext.direct.PollingProvider}
72069     url:  'php/poll.php'
72070 }, pollProv); // reference to previously created instance
72071      * </code></pre>
72072      * @param {Object/Array} provider Accepts either an Array of Provider descriptions (an instance
72073      * or config object for a Provider) or any number of Provider descriptions as arguments.  Each
72074      * Provider description instructs Ext.Direct how to create client-side stub methods.
72075      */
72076     addProvider : function(provider){
72077         var me = this,
72078             args = arguments,
72079             i = 0,
72080             len;
72081             
72082         if (args.length > 1) {
72083             for (len = args.length; i < len; ++i) {
72084                 me.addProvider(args[i]);
72085             }
72086             return;
72087         }
72088
72089         // if provider has not already been instantiated
72090         if (!provider.isProvider) {
72091             provider = Ext.create('direct.' + provider.type + 'provider', provider);
72092         }
72093         me.providers.add(provider);
72094         provider.on('data', me.onProviderData, me);
72095
72096
72097         if (!provider.isConnected()) {
72098             provider.connect();
72099         }
72100
72101         return provider;
72102     },
72103     
72104     /**
72105      * Retrieve a {@link Ext.direct.Provider provider} by the
72106      * <b><tt>{@link Ext.direct.Provider#id id}</tt></b> specified when the provider is
72107      * {@link #addProvider added}.
72108      * @param {String/Ext.data.Provider} id The id of the provider, or the provider instance.
72109      */
72110     getProvider : function(id){
72111         return id.isProvider ? id : this.providers.get(id);
72112     },
72113     
72114     /**
72115      * Removes the provider.
72116      * @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider.
72117      * @return {Ext.direct.Provider} The provider, null if not found.
72118      */
72119     removeProvider : function(provider){
72120         var me = this,
72121             providers = me.providers,
72122             provider = provider.isProvider ? provider : providers.get(provider);
72123             
72124         if (provider) {
72125             provider.un('data', me.onProviderData, me);
72126             providers.remove(provider);
72127             return provider;
72128         }
72129         return null;
72130     },
72131     
72132     /**
72133      * Add a transaction to the manager.
72134      * @private
72135      * @param {Ext.direct.Transaction} transaction The transaction to add
72136      * @return {Ext.direct.Transaction} transaction
72137      */
72138     addTransaction: function(transaction){
72139         this.transactions.add(transaction);
72140         return transaction;
72141     },
72142
72143     /**
72144      * Remove a transaction from the manager.
72145      * @private
72146      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove
72147      * @return {Ext.direct.Transaction} transaction
72148      */
72149     removeTransaction: function(transaction){
72150         transaction = this.getTransaction(transaction);
72151         this.transactions.remove(transaction);
72152         return transaction;
72153     },
72154
72155     /**
72156      * Gets a transaction
72157      * @private
72158      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to get
72159      * @return {Ext.direct.Transaction}
72160      */
72161     getTransaction: function(transaction){
72162         return transaction.isTransaction ? transaction : this.transactions.get(transaction);
72163     },
72164     
72165     onProviderData : function(provider, event){
72166         var me = this,
72167             i = 0,
72168             len;
72169             
72170         if (Ext.isArray(event)) {
72171             for (len = event.length; i < len; ++i) {
72172                 me.onProviderData(provider, event[i]);
72173             }
72174             return;
72175         }
72176         if (event.name && event.name != 'event' && event.name != 'exception') {
72177             me.fireEvent(event.name, event);
72178         } else if (event.type == 'exception') {
72179             me.fireEvent('exception', event);
72180         }
72181         me.fireEvent('event', event, provider);
72182     }
72183 }, function(){
72184     // Backwards compatibility
72185     Ext.Direct = Ext.direct.Manager;
72186 });
72187
72188 /**
72189  * @class Ext.data.proxy.Direct
72190  * @extends Ext.data.proxy.Server
72191  * 
72192  * This class is used to send requests to the server using {@link Ext.direct}. When a request is made,
72193  * the transport mechanism is handed off to the appropriate {@link Ext.direct.RemotingProvider Provider}
72194  * to complete the call.
72195  * 
72196  * ## Specifying the function
72197  * This proxy expects a Direct remoting method to be passed in order to be able to complete requests.
72198  * This can be done by specifying the {@link #directFn} configuration. This will use the same direct
72199  * method for all requests. Alternatively, you can provide an {@link #api} configuration. This
72200  * allows you to specify a different remoting method for each CRUD action.
72201  * 
72202  * ## Paramaters
72203  * This proxy provides options to help configure which parameters will be sent to the server.
72204  * By specifying the {@link #paramsAsHash} option, it will send an object literal containing each
72205  * of the passed parameters. The {@link #paramOrder} option can be used to specify the order in which
72206  * the remoting method parameters are passed.
72207  * 
72208  * ## Example Usage
72209  * 
72210  *     Ext.define('User', {
72211  *         extend: 'Ext.data.Model',
72212  *         fields: ['firstName', 'lastName'],
72213  *         proxy: {
72214  *             type: 'direct',
72215  *             directFn: MyApp.getUsers,
72216  *             paramOrder: 'id' // Tells the proxy to pass the id as the first parameter to the remoting method.
72217  *         }
72218  *     });
72219  *     User.load(1);
72220  */
72221 Ext.define('Ext.data.proxy.Direct', {
72222     /* Begin Definitions */
72223     
72224     extend: 'Ext.data.proxy.Server',
72225     alternateClassName: 'Ext.data.DirectProxy',
72226     
72227     alias: 'proxy.direct',
72228     
72229     requires: ['Ext.direct.Manager'],
72230     
72231     /* End Definitions */
72232    
72233    /**
72234      * @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. A list of params to be executed
72235      * server side.  Specify the params in the order in which they must be executed on the server-side
72236      * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,
72237      * comma, or pipe. For example,
72238      * any of the following would be acceptable:<pre><code>
72239 paramOrder: ['param1','param2','param3']
72240 paramOrder: 'param1 param2 param3'
72241 paramOrder: 'param1,param2,param3'
72242 paramOrder: 'param1|param2|param'
72243      </code></pre>
72244      */
72245     paramOrder: undefined,
72246
72247     /**
72248      * @cfg {Boolean} paramsAsHash
72249      * Send parameters as a collection of named arguments (defaults to <tt>true</tt>). Providing a
72250      * <tt>{@link #paramOrder}</tt> nullifies this configuration.
72251      */
72252     paramsAsHash: true,
72253
72254     /**
72255      * @cfg {Function} directFn
72256      * Function to call when executing a request.  directFn is a simple alternative to defining the api configuration-parameter
72257      * for Store's which will not implement a full CRUD api.
72258      */
72259     directFn : undefined,
72260     
72261     /**
72262      * @cfg {Object} api The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
72263      * function call.
72264      */
72265     
72266     /**
72267      * @cfg {Object} extraParams Extra parameters that will be included on every read request. Individual requests with params
72268      * of the same name will override these params when they are in conflict.
72269      */
72270     
72271     // private
72272     paramOrderRe: /[\s,|]/,
72273     
72274     constructor: function(config){
72275         var me = this;
72276         
72277         Ext.apply(me, config);
72278         if (Ext.isString(me.paramOrder)) {
72279             me.paramOrder = me.paramOrder.split(me.paramOrderRe);
72280         }
72281         me.callParent(arguments);
72282     },
72283     
72284     doRequest: function(operation, callback, scope) {
72285         var me = this,
72286             writer = me.getWriter(),
72287             request = me.buildRequest(operation, callback, scope),
72288             fn = me.api[request.action]  || me.directFn,
72289             args = [],
72290             params = request.params,
72291             paramOrder = me.paramOrder,
72292             method,
72293             i = 0,
72294             len;
72295             
72296         if (!fn) {
72297             Ext.Error.raise('No direct function specified for this proxy');
72298         }
72299             
72300         if (operation.allowWrite()) {
72301             request = writer.write(request);
72302         }
72303         
72304         if (operation.action == 'read') {
72305             // We need to pass params
72306             method = fn.directCfg.method;
72307             
72308             if (method.ordered) {
72309                 if (method.len > 0) {
72310                     if (paramOrder) {
72311                         for (len = paramOrder.length; i < len; ++i) {
72312                             args.push(params[paramOrder[i]]);
72313                         }
72314                     } else if (me.paramsAsHash) {
72315                         args.push(params);
72316                     }
72317                 }
72318             } else {
72319                 args.push(params);
72320             }
72321         } else {
72322             args.push(request.jsonData);
72323         }
72324         
72325         Ext.apply(request, {
72326             args: args,
72327             directFn: fn
72328         });
72329         args.push(me.createRequestCallback(request, operation, callback, scope), me);
72330         fn.apply(window, args);
72331     },
72332     
72333     /*
72334      * Inherit docs. We don't apply any encoding here because
72335      * all of the direct requests go out as jsonData
72336      */
72337     applyEncoding: function(value){
72338         return value;
72339     },
72340     
72341     createRequestCallback: function(request, operation, callback, scope){
72342         var me = this;
72343         
72344         return function(data, event){
72345             me.processResponse(event.status, operation, request, event, callback, scope);
72346         };
72347     },
72348     
72349     // inherit docs
72350     extractResponseData: function(response){
72351         return Ext.isDefined(response.result) ? response.result : response.data;
72352     },
72353     
72354     // inherit docs
72355     setException: function(operation, response) {
72356         operation.setException(response.message);
72357     },
72358     
72359     // inherit docs
72360     buildUrl: function(){
72361         return '';
72362     }
72363 });
72364
72365 /**
72366  * @class Ext.data.DirectStore
72367  * @extends Ext.data.Store
72368  * <p>Small helper class to create an {@link Ext.data.Store} configured with an
72369  * {@link Ext.data.proxy.Direct} and {@link Ext.data.reader.Json} to make interacting
72370  * with an {@link Ext.Direct} Server-side {@link Ext.direct.Provider Provider} easier.
72371  * To create a different proxy/reader combination create a basic {@link Ext.data.Store}
72372  * configured as needed.</p>
72373  *
72374  * <p><b>*Note:</b> Although they are not listed, this class inherits all of the config options of:</p>
72375  * <div><ul class="mdetail-params">
72376  * <li><b>{@link Ext.data.Store Store}</b></li>
72377  * <div class="sub-desc"><ul class="mdetail-params">
72378  *
72379  * </ul></div>
72380  * <li><b>{@link Ext.data.reader.Json JsonReader}</b></li>
72381  * <div class="sub-desc"><ul class="mdetail-params">
72382  * <li><tt><b>{@link Ext.data.reader.Json#root root}</b></tt></li>
72383  * <li><tt><b>{@link Ext.data.reader.Json#idProperty idProperty}</b></tt></li>
72384  * <li><tt><b>{@link Ext.data.reader.Json#totalProperty totalProperty}</b></tt></li>
72385  * </ul></div>
72386  *
72387  * <li><b>{@link Ext.data.proxy.Direct DirectProxy}</b></li>
72388  * <div class="sub-desc"><ul class="mdetail-params">
72389  * <li><tt><b>{@link Ext.data.proxy.Direct#directFn directFn}</b></tt></li>
72390  * <li><tt><b>{@link Ext.data.proxy.Direct#paramOrder paramOrder}</b></tt></li>
72391  * <li><tt><b>{@link Ext.data.proxy.Direct#paramsAsHash paramsAsHash}</b></tt></li>
72392  * </ul></div>
72393  * </ul></div>
72394  *
72395  * @constructor
72396  * @param {Object} config
72397  */
72398
72399 Ext.define('Ext.data.DirectStore', {
72400     /* Begin Definitions */
72401     
72402     extend: 'Ext.data.Store',
72403     
72404     alias: 'store.direct',
72405     
72406     requires: ['Ext.data.proxy.Direct'],
72407    
72408     /* End Definitions */
72409    
72410    constructor : function(config){
72411         config = Ext.apply({}, config);
72412         if (!config.proxy) {
72413             var proxy = {
72414                 type: 'direct',
72415                 reader: {
72416                     type: 'json'
72417                 }
72418             };
72419             Ext.copyTo(proxy, config, 'paramOrder,paramsAsHash,directFn,api,simpleSortMode');
72420             Ext.copyTo(proxy.reader, config, 'totalProperty,root,idProperty');
72421             config.proxy = proxy;
72422         }
72423         this.callParent([config]);
72424     }    
72425 });
72426
72427 /**
72428  * @class Ext.util.Inflector
72429  * @extends Object
72430  * <p>General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and 
72431  * {@link #ordinalize ordinalizes} words. Sample usage:</p>
72432  * 
72433 <pre><code>
72434 //turning singular words into plurals
72435 Ext.util.Inflector.pluralize('word'); //'words'
72436 Ext.util.Inflector.pluralize('person'); //'people'
72437 Ext.util.Inflector.pluralize('sheep'); //'sheep'
72438
72439 //turning plurals into singulars
72440 Ext.util.Inflector.singularize('words'); //'word'
72441 Ext.util.Inflector.singularize('people'); //'person'
72442 Ext.util.Inflector.singularize('sheep'); //'sheep'
72443
72444 //ordinalizing numbers
72445 Ext.util.Inflector.ordinalize(11); //"11th"
72446 Ext.util.Inflector.ordinalize(21); //"21th"
72447 Ext.util.Inflector.ordinalize(1043); //"1043rd"
72448 </code></pre>
72449  * 
72450  * <p><u>Customization</u></p>
72451  * 
72452  * <p>The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
72453  * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
72454  * Here is how we might add a rule that pluralizes "ox" to "oxen":</p>
72455  * 
72456 <pre><code>
72457 Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
72458 </code></pre>
72459  * 
72460  * <p>Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string.
72461  * In this case, the regular expression will only match the string "ox", and will replace that match with "oxen". 
72462  * Here's how we could add the inverse rule:</p>
72463  * 
72464 <pre><code>
72465 Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
72466 </code></pre>
72467  * 
72468  * <p>Note that the ox/oxen rules are present by default.</p>
72469  * 
72470  * @singleton
72471  */
72472
72473 Ext.define('Ext.util.Inflector', {
72474
72475     /* Begin Definitions */
72476
72477     singleton: true,
72478
72479     /* End Definitions */
72480
72481     /**
72482      * @private
72483      * The registered plural tuples. Each item in the array should contain two items - the first must be a regular
72484      * expression that matchers the singular form of a word, the second must be a String that replaces the matched
72485      * part of the regular expression. This is managed by the {@link #plural} method.
72486      * @property plurals
72487      * @type Array
72488      */
72489     plurals: [
72490         [(/(quiz)$/i),                "$1zes"  ],
72491         [(/^(ox)$/i),                 "$1en"   ],
72492         [(/([m|l])ouse$/i),           "$1ice"  ],
72493         [(/(matr|vert|ind)ix|ex$/i),  "$1ices" ],
72494         [(/(x|ch|ss|sh)$/i),          "$1es"   ],
72495         [(/([^aeiouy]|qu)y$/i),       "$1ies"  ],
72496         [(/(hive)$/i),                "$1s"    ],
72497         [(/(?:([^f])fe|([lr])f)$/i),  "$1$2ves"],
72498         [(/sis$/i),                   "ses"    ],
72499         [(/([ti])um$/i),              "$1a"    ],
72500         [(/(buffal|tomat|potat)o$/i), "$1oes"  ],
72501         [(/(bu)s$/i),                 "$1ses"  ],
72502         [(/(alias|status|sex)$/i),    "$1es"   ],
72503         [(/(octop|vir)us$/i),         "$1i"    ],
72504         [(/(ax|test)is$/i),           "$1es"   ],
72505         [(/^person$/),                "people" ],
72506         [(/^man$/),                   "men"    ],
72507         [(/^(child)$/),               "$1ren"  ],
72508         [(/s$/i),                     "s"      ],
72509         [(/$/),                       "s"      ]
72510     ],
72511     
72512     /**
72513      * @private
72514      * The set of registered singular matchers. Each item in the array should contain two items - the first must be a 
72515      * regular expression that matches the plural form of a word, the second must be a String that replaces the 
72516      * matched part of the regular expression. This is managed by the {@link #singular} method.
72517      * @property singulars
72518      * @type Array
72519      */
72520     singulars: [
72521       [(/(quiz)zes$/i),                                                    "$1"     ],
72522       [(/(matr)ices$/i),                                                   "$1ix"   ],
72523       [(/(vert|ind)ices$/i),                                               "$1ex"   ],
72524       [(/^(ox)en/i),                                                       "$1"     ],
72525       [(/(alias|status)es$/i),                                             "$1"     ],
72526       [(/(octop|vir)i$/i),                                                 "$1us"   ],
72527       [(/(cris|ax|test)es$/i),                                             "$1is"   ],
72528       [(/(shoe)s$/i),                                                      "$1"     ],
72529       [(/(o)es$/i),                                                        "$1"     ],
72530       [(/(bus)es$/i),                                                      "$1"     ],
72531       [(/([m|l])ice$/i),                                                   "$1ouse" ],
72532       [(/(x|ch|ss|sh)es$/i),                                               "$1"     ],
72533       [(/(m)ovies$/i),                                                     "$1ovie" ],
72534       [(/(s)eries$/i),                                                     "$1eries"],
72535       [(/([^aeiouy]|qu)ies$/i),                                            "$1y"    ],
72536       [(/([lr])ves$/i),                                                    "$1f"    ],
72537       [(/(tive)s$/i),                                                      "$1"     ],
72538       [(/(hive)s$/i),                                                      "$1"     ],
72539       [(/([^f])ves$/i),                                                    "$1fe"   ],
72540       [(/(^analy)ses$/i),                                                  "$1sis"  ],
72541       [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
72542       [(/([ti])a$/i),                                                      "$1um"   ],
72543       [(/(n)ews$/i),                                                       "$1ews"  ],
72544       [(/people$/i),                                                       "person" ],
72545       [(/s$/i),                                                            ""       ]
72546     ],
72547     
72548     /**
72549      * @private
72550      * The registered uncountable words
72551      * @property uncountable
72552      * @type Array
72553      */
72554      uncountable: [
72555         "sheep",
72556         "fish",
72557         "series",
72558         "species",
72559         "money",
72560         "rice",
72561         "information",
72562         "equipment",
72563         "grass",
72564         "mud",
72565         "offspring",
72566         "deer",
72567         "means"
72568     ],
72569     
72570     /**
72571      * Adds a new singularization rule to the Inflector. See the intro docs for more information
72572      * @param {RegExp} matcher The matcher regex
72573      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
72574      */
72575     singular: function(matcher, replacer) {
72576         this.singulars.unshift([matcher, replacer]);
72577     },
72578     
72579     /**
72580      * Adds a new pluralization rule to the Inflector. See the intro docs for more information
72581      * @param {RegExp} matcher The matcher regex
72582      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
72583      */
72584     plural: function(matcher, replacer) {
72585         this.plurals.unshift([matcher, replacer]);
72586     },
72587     
72588     /**
72589      * Removes all registered singularization rules
72590      */
72591     clearSingulars: function() {
72592         this.singulars = [];
72593     },
72594     
72595     /**
72596      * Removes all registered pluralization rules
72597      */
72598     clearPlurals: function() {
72599         this.plurals = [];
72600     },
72601     
72602     /**
72603      * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
72604      * @param {String} word The word to test
72605      * @return {Boolean} True if the word is transnumeral
72606      */
72607     isTransnumeral: function(word) {
72608         return Ext.Array.indexOf(this.uncountable, word) != -1;
72609     },
72610
72611     /**
72612      * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
72613      * @param {String} word The word to pluralize
72614      * @return {String} The pluralized form of the word
72615      */
72616     pluralize: function(word) {
72617         if (this.isTransnumeral(word)) {
72618             return word;
72619         }
72620
72621         var plurals = this.plurals,
72622             length  = plurals.length,
72623             tuple, regex, i;
72624         
72625         for (i = 0; i < length; i++) {
72626             tuple = plurals[i];
72627             regex = tuple[0];
72628             
72629             if (regex == word || (regex.test && regex.test(word))) {
72630                 return word.replace(regex, tuple[1]);
72631             }
72632         }
72633         
72634         return word;
72635     },
72636     
72637     /**
72638      * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
72639      * @param {String} word The word to singularize
72640      * @return {String} The singularized form of the word
72641      */
72642     singularize: function(word) {
72643         if (this.isTransnumeral(word)) {
72644             return word;
72645         }
72646
72647         var singulars = this.singulars,
72648             length    = singulars.length,
72649             tuple, regex, i;
72650         
72651         for (i = 0; i < length; i++) {
72652             tuple = singulars[i];
72653             regex = tuple[0];
72654             
72655             if (regex == word || (regex.test && regex.test(word))) {
72656                 return word.replace(regex, tuple[1]);
72657             }
72658         }
72659         
72660         return word;
72661     },
72662     
72663     /**
72664      * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data 
72665      * package
72666      * @param {String} word The word to classify
72667      * @return {String} The classified version of the word
72668      */
72669     classify: function(word) {
72670         return Ext.String.capitalize(this.singularize(word));
72671     },
72672     
72673     /**
72674      * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the 
72675      * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
72676      * @param {Number} number The number to ordinalize
72677      * @return {String} The ordinalized number
72678      */
72679     ordinalize: function(number) {
72680         var parsed = parseInt(number, 10),
72681             mod10  = parsed % 10,
72682             mod100 = parsed % 100;
72683         
72684         //11 through 13 are a special case
72685         if (11 <= mod100 && mod100 <= 13) {
72686             return number + "th";
72687         } else {
72688             switch(mod10) {
72689                 case 1 : return number + "st";
72690                 case 2 : return number + "nd";
72691                 case 3 : return number + "rd";
72692                 default: return number + "th";
72693             }
72694         }
72695     }
72696 }, function() {
72697     //aside from the rules above, there are a number of words that have irregular pluralization so we add them here
72698     var irregulars = {
72699             alumnus: 'alumni',
72700             cactus : 'cacti',
72701             focus  : 'foci',
72702             nucleus: 'nuclei',
72703             radius: 'radii',
72704             stimulus: 'stimuli',
72705             ellipsis: 'ellipses',
72706             paralysis: 'paralyses',
72707             oasis: 'oases',
72708             appendix: 'appendices',
72709             index: 'indexes',
72710             beau: 'beaux',
72711             bureau: 'bureaux',
72712             tableau: 'tableaux',
72713             woman: 'women',
72714             child: 'children',
72715             man: 'men',
72716             corpus:     'corpora',
72717             criterion: 'criteria',
72718             curriculum: 'curricula',
72719             genus: 'genera',
72720             memorandum: 'memoranda',
72721             phenomenon: 'phenomena',
72722             foot: 'feet',
72723             goose: 'geese',
72724             tooth: 'teeth',
72725             antenna: 'antennae',
72726             formula: 'formulae',
72727             nebula: 'nebulae',
72728             vertebra: 'vertebrae',
72729             vita: 'vitae'
72730         },
72731         singular;
72732     
72733     for (singular in irregulars) {
72734         this.plural(singular, irregulars[singular]);
72735         this.singular(irregulars[singular], singular);
72736     }
72737 });
72738 /**
72739  * @author Ed Spencer
72740  * @class Ext.data.HasManyAssociation
72741  * @extends Ext.data.Association
72742  * 
72743  * <p>Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:</p>
72744  * 
72745 <pre><code>
72746 Ext.define('Product', {
72747     extend: 'Ext.data.Model',
72748     fields: [
72749         {name: 'id',      type: 'int'},
72750         {name: 'user_id', type: 'int'},
72751         {name: 'name',    type: 'string'}
72752     ]
72753 });
72754
72755 Ext.define('User', {
72756     extend: 'Ext.data.Model',
72757     fields: [
72758         {name: 'id',   type: 'int'},
72759         {name: 'name', type: 'string'}
72760     ],
72761     // we can use the hasMany shortcut on the model to create a hasMany association
72762     hasMany: {model: 'Product', name: 'products'}
72763 });
72764 </pre></code>
72765
72766  * <p>Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives
72767  * us a new function on every User instance, in this case the function is called 'products' because that is the name
72768  * we specified in the association configuration above.</p>
72769  * 
72770  * <p>This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load
72771  * only Products for the given model instance:</p>
72772  * 
72773 <pre><code>
72774 //first, we load up a User with id of 1
72775 var user = Ext.ModelManager.create({id: 1, name: 'Ed'}, 'User');
72776
72777 //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
72778 //the created store is automatically scoped to the set of Products for the User with id of 1
72779 var products = user.products();
72780
72781 //we still have all of the usual Store functions, for example it's easy to add a Product for this User
72782 products.add({
72783     name: 'Another Product'
72784 });
72785
72786 //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
72787 products.sync();
72788 </code></pre>
72789  * 
72790  * <p>The new Store is only instantiated the first time you call products() to conserve memory and processing time,
72791  * though calling products() a second time returns the same store instance.</p>
72792  * 
72793  * <p><u>Custom filtering</u></p>
72794  * 
72795  * <p>The Store is automatically furnished with a filter - by default this filter tells the store to only return
72796  * records where the associated model's foreign key matches the owner model's primary key. For example, if a User
72797  * with ID = 100 hasMany Products, the filter loads only Products with user_id == 100.</p>
72798  * 
72799  * <p>Sometimes we want to filter by another field - for example in the case of a Twitter search application we may
72800  * have models for Search and Tweet:</p>
72801  * 
72802 <pre><code>
72803 Ext.define('Search', {
72804     extend: 'Ext.data.Model',
72805     fields: [
72806         'id', 'query'
72807     ],
72808
72809     hasMany: {
72810         model: 'Tweet',
72811         name : 'tweets',
72812         filterProperty: 'query'
72813     }
72814 });
72815
72816 Ext.define('Tweet', {
72817     extend: 'Ext.data.Model',
72818     fields: [
72819         'id', 'text', 'from_user'
72820     ]
72821 });
72822
72823 //returns a Store filtered by the filterProperty
72824 var store = new Search({query: 'Sencha Touch'}).tweets();
72825 </code></pre>
72826  * 
72827  * <p>The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
72828  * equivalent to this:</p>
72829  * 
72830 <pre><code>
72831 var store = new Ext.data.Store({
72832     model: 'Tweet',
72833     filters: [
72834         {
72835             property: 'query',
72836             value   : 'Sencha Touch'
72837         }
72838     ]
72839 });
72840 </code></pre>
72841  */
72842 Ext.define('Ext.data.HasManyAssociation', {
72843     extend: 'Ext.data.Association',
72844     requires: ['Ext.util.Inflector'],
72845
72846     alias: 'association.hasmany',
72847
72848     /**
72849      * @cfg {String} foreignKey The name of the foreign key on the associated model that links it to the owner
72850      * model. Defaults to the lowercased name of the owner model plus "_id", e.g. an association with a where a
72851      * model called Group hasMany Users would create 'group_id' as the foreign key. When the remote store is loaded,
72852      * the store is automatically filtered so that only records with a matching foreign key are included in the 
72853      * resulting child store. This can be overridden by specifying the {@link #filterProperty}.
72854      * <pre><code>
72855 Ext.define('Group', {
72856     extend: 'Ext.data.Model',
72857     fields: ['id', 'name'],
72858     hasMany: 'User'
72859 });
72860
72861 Ext.define('User', {
72862     extend: 'Ext.data.Model',
72863     fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
72864     belongsTo: 'Group'
72865 });
72866      * </code></pre>
72867      */
72868     
72869     /**
72870      * @cfg {String} name The name of the function to create on the owner model to retrieve the child store.
72871      * If not specified, the pluralized name of the child model is used.
72872      * <pre><code>
72873 // This will create a users() method on any Group model instance
72874 Ext.define('Group', {
72875     extend: 'Ext.data.Model',
72876     fields: ['id', 'name'],
72877     hasMany: 'User'
72878 });
72879 var group = new Group();
72880 console.log(group.users());
72881
72882 // The method to retrieve the users will now be getUserList
72883 Ext.define('Group', {
72884     extend: 'Ext.data.Model',
72885     fields: ['id', 'name'],
72886     hasMany: {model: 'User', name: 'getUserList'}
72887 });
72888 var group = new Group();
72889 console.log(group.getUserList());
72890      * </code></pre>
72891      */
72892     
72893     /**
72894      * @cfg {Object} storeConfig Optional configuration object that will be passed to the generated Store. Defaults to 
72895      * undefined.
72896      */
72897     
72898     /**
72899      * @cfg {String} filterProperty Optionally overrides the default filter that is set up on the associated Store. If
72900      * this is not set, a filter is automatically created which filters the association based on the configured 
72901      * {@link #foreignKey}. See intro docs for more details. Defaults to undefined
72902      */
72903     
72904     /**
72905      * @cfg {Boolean} autoLoad True to automatically load the related store from a remote source when instantiated.
72906      * Defaults to <tt>false</tt>.
72907      */
72908     
72909     /**
72910      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
72911      * Use 'hasMany' to create a HasManyAssocation
72912      * <pre><code>
72913 associations: [{
72914     type: 'hasMany',
72915     model: 'User'
72916 }]
72917      * </code></pre>
72918      */
72919     
72920     constructor: function(config) {
72921         var me = this,
72922             ownerProto,
72923             name;
72924             
72925         me.callParent(arguments);
72926         
72927         me.name = me.name || Ext.util.Inflector.pluralize(me.associatedName.toLowerCase());
72928         
72929         ownerProto = me.ownerModel.prototype;
72930         name = me.name;
72931         
72932         Ext.applyIf(me, {
72933             storeName : name + "Store",
72934             foreignKey: me.ownerName.toLowerCase() + "_id"
72935         });
72936         
72937         ownerProto[name] = me.createStore();
72938     },
72939     
72940     /**
72941      * @private
72942      * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
72943      * by the owner model's primary key - e.g. in a hasMany association where Group hasMany Users, this function
72944      * returns a Store configured to return the filtered set of a single Group's Users.
72945      * @return {Function} The store-generating function
72946      */
72947     createStore: function() {
72948         var that            = this,
72949             associatedModel = that.associatedModel,
72950             storeName       = that.storeName,
72951             foreignKey      = that.foreignKey,
72952             primaryKey      = that.primaryKey,
72953             filterProperty  = that.filterProperty,
72954             autoLoad        = that.autoLoad,
72955             storeConfig     = that.storeConfig || {};
72956         
72957         return function() {
72958             var me = this,
72959                 config, filter,
72960                 modelDefaults = {};
72961                 
72962             if (me[storeName] === undefined) {
72963                 if (filterProperty) {
72964                     filter = {
72965                         property  : filterProperty,
72966                         value     : me.get(filterProperty),
72967                         exactMatch: true
72968                     };
72969                 } else {
72970                     filter = {
72971                         property  : foreignKey,
72972                         value     : me.get(primaryKey),
72973                         exactMatch: true
72974                     };
72975                 }
72976                 
72977                 modelDefaults[foreignKey] = me.get(primaryKey);
72978                 
72979                 config = Ext.apply({}, storeConfig, {
72980                     model        : associatedModel,
72981                     filters      : [filter],
72982                     remoteFilter : false,
72983                     modelDefaults: modelDefaults
72984                 });
72985                 
72986                 me[storeName] = Ext.create('Ext.data.Store', config);
72987                 if (autoLoad) {
72988                     me[storeName].load();
72989                 }
72990             }
72991             
72992             return me[storeName];
72993         };
72994     },
72995     
72996     /**
72997      * Read associated data
72998      * @private
72999      * @param {Ext.data.Model} record The record we're writing to
73000      * @param {Ext.data.reader.Reader} reader The reader for the associated model
73001      * @param {Object} associationData The raw associated data
73002      */
73003     read: function(record, reader, associationData){
73004         var store = record[this.name](),
73005             inverse;
73006     
73007         store.add(reader.read(associationData).records);
73008     
73009         //now that we've added the related records to the hasMany association, set the inverse belongsTo
73010         //association on each of them if it exists
73011         inverse = this.associatedModel.prototype.associations.findBy(function(assoc){
73012             return assoc.type === 'belongsTo' && assoc.associatedName === record.$className;
73013         });
73014     
73015         //if the inverse association was found, set it now on each record we've just created
73016         if (inverse) {
73017             store.data.each(function(associatedRecord){
73018                 associatedRecord[inverse.instanceName] = record;
73019             });
73020         }
73021     }
73022 });
73023 /**
73024  * @class Ext.data.JsonP
73025  * @singleton
73026  * This class is used to create JSONP requests. JSONP is a mechanism that allows for making
73027  * requests for data cross domain. More information is available here:
73028  * http://en.wikipedia.org/wiki/JSONP
73029  */
73030 Ext.define('Ext.data.JsonP', {
73031     
73032     /* Begin Definitions */
73033     
73034     singleton: true,
73035     
73036     statics: {
73037         requestCount: 0,
73038         requests: {}
73039     },
73040     
73041     /* End Definitions */
73042     
73043     /**
73044      * @property timeout
73045      * @type Number
73046      * A default timeout for any JsonP requests. If the request has not completed in this time the
73047      * failure callback will be fired. The timeout is in ms. Defaults to <tt>30000</tt>.
73048      */
73049     timeout: 30000,
73050     
73051     /**
73052      * @property disableCaching
73053      * @type Boolean
73054      * True to add a unique cache-buster param to requests. Defaults to <tt>true</tt>.
73055      */
73056     disableCaching: true,
73057    
73058     /**
73059      * @property disableCachingParam 
73060      * @type String
73061      * Change the parameter which is sent went disabling caching through a cache buster. Defaults to <tt>'_dc'</tt>.
73062      */
73063     disableCachingParam: '_dc',
73064    
73065     /**
73066      * @property callbackKey
73067      * @type String
73068      * Specifies the GET parameter that will be sent to the server containing the function name to be executed when
73069      * the request completes. Defaults to <tt>callback</tt>. Thus, a common request will be in the form of
73070      * url?callback=Ext.data.JsonP.callback1
73071      */
73072     callbackKey: 'callback',
73073    
73074     /**
73075      * Makes a JSONP request.
73076      * @param {Object} options An object which may contain the following properties. Note that options will
73077      * take priority over any defaults that are specified in the class.
73078      * <ul>
73079      * <li><b>url</b> : String <div class="sub-desc">The URL to request.</div></li>
73080      * <li><b>params</b> : Object (Optional)<div class="sub-desc">An object containing a series of
73081      * key value pairs that will be sent along with the request.</div></li>
73082      * <li><b>timeout</b> : Number (Optional) <div class="sub-desc">See {@link #timeout}</div></li>
73083      * <li><b>callbackKey</b> : String (Optional) <div class="sub-desc">See {@link #callbackKey}</div></li>
73084      * <li><b>callbackName</b> : String (Optional) <div class="sub-desc">The function name to use for this request.
73085      * By default this name will be auto-generated: Ext.data.JsonP.callback1, Ext.data.JsonP.callback2, etc.
73086      * Setting this option to "my_name" will force the function name to be Ext.data.JsonP.my_name.
73087      * Use this if you want deterministic behavior, but be careful - the callbackName should be different
73088      * in each JsonP request that you make.</div></li>
73089      * <li><b>disableCaching</b> : Boolean (Optional) <div class="sub-desc">See {@link #disableCaching}</div></li>
73090      * <li><b>disableCachingParam</b> : String (Optional) <div class="sub-desc">See {@link #disableCachingParam}</div></li>
73091      * <li><b>success</b> : Function (Optional) <div class="sub-desc">A function to execute if the request succeeds.</div></li>
73092      * <li><b>failure</b> : Function (Optional) <div class="sub-desc">A function to execute if the request fails.</div></li>
73093      * <li><b>callback</b> : Function (Optional) <div class="sub-desc">A function to execute when the request 
73094      * completes, whether it is a success or failure.</div></li>
73095      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
73096      * which to execute the callbacks: The "this" object for the callback function. Defaults to the browser window.</div></li>
73097      * </ul>
73098      * @return {Object} request An object containing the request details.
73099      */
73100     request: function(options){
73101         options = Ext.apply({}, options);
73102        
73103         if (!options.url) {
73104             Ext.Error.raise('A url must be specified for a JSONP request.');
73105         }
73106         
73107         var me = this, 
73108             disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching, 
73109             cacheParam = options.disableCachingParam || me.disableCachingParam, 
73110             id = ++me.statics().requestCount, 
73111             callbackName = options.callbackName || 'callback' + id, 
73112             callbackKey = options.callbackKey || me.callbackKey, 
73113             timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout, 
73114             params = Ext.apply({}, options.params), 
73115             url = options.url,
73116             request, 
73117             script;
73118             
73119         params[callbackKey] = 'Ext.data.JsonP.' + callbackName;
73120         if (disableCaching) {
73121             params[cacheParam] = new Date().getTime();
73122         }
73123         
73124         script = me.createScript(url, params);
73125         
73126         me.statics().requests[id] = request = {
73127             url: url,
73128             params: params,
73129             script: script,
73130             id: id,
73131             scope: options.scope,
73132             success: options.success,
73133             failure: options.failure,
73134             callback: options.callback,
73135             callbackName: callbackName
73136         };
73137         
73138         if (timeout > 0) {
73139             request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
73140         }
73141         
73142         me.setupErrorHandling(request);
73143         me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
73144         Ext.getHead().appendChild(script);
73145         return request;
73146     },
73147     
73148     /**
73149      * Abort a request. If the request parameter is not specified all open requests will
73150      * be aborted.
73151      * @param {Object/String} request (Optional) The request to abort
73152      */
73153     abort: function(request){
73154         var requests = this.statics().requests,
73155             key;
73156             
73157         if (request) {
73158             if (!request.id) {
73159                 request = requests[request];
73160             }
73161             this.abort(request);
73162         } else {
73163             for (key in requests) {
73164                 if (requests.hasOwnProperty(key)) {
73165                     this.abort(requests[key]);
73166                 }
73167             }
73168         }
73169     },
73170     
73171     /**
73172      * Sets up error handling for the script
73173      * @private
73174      * @param {Object} request The request
73175      */
73176     setupErrorHandling: function(request){
73177         request.script.onerror = Ext.bind(this.handleError, this, [request]);
73178     },
73179     
73180     /**
73181      * Handles any aborts when loading the script
73182      * @private
73183      * @param {Object} request The request
73184      */
73185     handleAbort: function(request){
73186         request.errorType = 'abort';
73187         this.handleResponse(null, request);
73188     },
73189     
73190     /**
73191      * Handles any script errors when loading the script
73192      * @private
73193      * @param {Object} request The request
73194      */
73195     handleError: function(request){
73196         request.errorType = 'error';
73197         this.handleResponse(null, request);
73198     },
73199  
73200     /**
73201      * Cleans up anu script handling errors
73202      * @private
73203      * @param {Object} request The request
73204      */
73205     cleanupErrorHandling: function(request){
73206         request.script.onerror = null;
73207     },
73208  
73209     /**
73210      * Handle any script timeouts
73211      * @private
73212      * @param {Object} request The request
73213      */
73214     handleTimeout: function(request){
73215         request.errorType = 'timeout';
73216         this.handleResponse(null, request);
73217     },
73218  
73219     /**
73220      * Handle a successful response
73221      * @private
73222      * @param {Object} result The result from the request
73223      * @param {Object} request The request
73224      */
73225     handleResponse: function(result, request){
73226  
73227         var success = true;
73228  
73229         if (request.timeout) {
73230             clearTimeout(request.timeout);
73231         }
73232         delete this[request.callbackName];
73233         delete this.statics()[request.id];
73234         this.cleanupErrorHandling(request);
73235         Ext.fly(request.script).remove();
73236  
73237         if (request.errorType) {
73238             success = false;
73239             Ext.callback(request.failure, request.scope, [request.errorType]);
73240         } else {
73241             Ext.callback(request.success, request.scope, [result]);
73242         }
73243         Ext.callback(request.callback, request.scope, [success, result, request.errorType]);
73244     },
73245     
73246     /**
73247      * Create the script tag
73248      * @private
73249      * @param {String} url The url of the request
73250      * @param {Object} params Any extra params to be sent
73251      */
73252     createScript: function(url, params) {
73253         var script = document.createElement('script');
73254         script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
73255         script.setAttribute("async", true);
73256         script.setAttribute("type", "text/javascript");
73257         return script;
73258     }
73259 });
73260
73261 /**
73262  * @class Ext.data.JsonPStore
73263  * @extends Ext.data.Store
73264  * @ignore
73265  * @private
73266  * <p><b>NOTE:</b> This class is in need of migration to the new API.</p>
73267  * <p>Small helper class to make creating {@link Ext.data.Store}s from different domain JSON data easier.
73268  * A JsonPStore will be automatically configured with a {@link Ext.data.reader.Json} and a {@link Ext.data.proxy.JsonP JsonPProxy}.</p>
73269  * <p>A store configuration would be something like:<pre><code>
73270 var store = new Ext.data.JsonPStore({
73271     // store configs
73272     autoDestroy: true,
73273     storeId: 'myStore',
73274
73275     // proxy configs
73276     url: 'get-images.php',
73277
73278     // reader configs
73279     root: 'images',
73280     idProperty: 'name',
73281     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
73282 });
73283  * </code></pre></p>
73284  * <p>This store is configured to consume a returned object of the form:<pre><code>
73285 stcCallback({
73286     images: [
73287         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
73288         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
73289     ]
73290 })
73291  * </code></pre>
73292  * <p>Where stcCallback is the callback name passed in the request to the remote domain. See {@link Ext.data.proxy.JsonP JsonPProxy}
73293  * for details of how this works.</p>
73294  * An object literal of this form could also be used as the {@link #data} config option.</p>
73295  * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
73296  * <b>{@link Ext.data.reader.Json JsonReader}</b> and <b>{@link Ext.data.proxy.JsonP JsonPProxy}</b>.</p>
73297  * @constructor
73298  * @param {Object} config
73299  * @xtype jsonpstore
73300  */
73301 Ext.define('Ext.data.JsonPStore', {
73302     extend: 'Ext.data.Store',
73303     alias : 'store.jsonp',
73304
73305     /**
73306      * @cfg {Ext.data.DataReader} reader @hide
73307      */
73308     constructor: function(config) {
73309         this.callParent(Ext.apply(config, {
73310             reader: Ext.create('Ext.data.reader.Json', config),
73311             proxy : Ext.create('Ext.data.proxy.JsonP', config)
73312         }));
73313     }
73314 });
73315
73316 /**
73317  * @class Ext.data.NodeInterface
73318  * This class is meant to be used as a set of methods that are applied to the prototype of a
73319  * Record to decorate it with a Node API. This means that models used in conjunction with a tree
73320  * will have all of the tree related methods available on the model. In general this class will
73321  * not be used directly by the developer.
73322  */
73323 Ext.define('Ext.data.NodeInterface', {
73324     requires: ['Ext.data.Field'],
73325     
73326     statics: {
73327         /**
73328          * This method allows you to decorate a Record's prototype to implement the NodeInterface.
73329          * This adds a set of methods, new events, new properties and new fields on every Record
73330          * with the same Model as the passed Record.
73331          * @param {Ext.data.Record} record The Record you want to decorate the prototype of.
73332          * @static
73333          */
73334         decorate: function(record) {
73335             if (!record.isNode) {
73336                 // Apply the methods and fields to the prototype
73337                 // @TODO: clean this up to use proper class system stuff
73338                 var mgr = Ext.ModelManager,
73339                     modelName = record.modelName,
73340                     modelClass = mgr.getModel(modelName),
73341                     idName = modelClass.prototype.idProperty,
73342                     instances = Ext.Array.filter(mgr.all.getArray(), function(item) {
73343                         return item.modelName == modelName;
73344                     }),
73345                     iln = instances.length,
73346                     newFields = [],
73347                     i, instance, jln, j, newField;
73348
73349                 // Start by adding the NodeInterface methods to the Model's prototype
73350                 modelClass.override(this.getPrototypeBody());
73351                 newFields = this.applyFields(modelClass, [
73352                     {name: idName,      type: 'string',  defaultValue: null},
73353                     {name: 'parentId',  type: 'string',  defaultValue: null},
73354                     {name: 'index',     type: 'int',     defaultValue: null},
73355                     {name: 'depth',     type: 'int',     defaultValue: 0}, 
73356                     {name: 'expanded',  type: 'bool',    defaultValue: false, persist: false},
73357                     {name: 'checked',   type: 'auto',    defaultValue: null},
73358                     {name: 'leaf',      type: 'bool',    defaultValue: false, persist: false},
73359                     {name: 'cls',       type: 'string',  defaultValue: null, persist: false},
73360                     {name: 'iconCls',   type: 'string',  defaultValue: null, persist: false},
73361                     {name: 'root',      type: 'boolean', defaultValue: false, persist: false},
73362                     {name: 'isLast',    type: 'boolean', defaultValue: false, persist: false},
73363                     {name: 'isFirst',   type: 'boolean', defaultValue: false, persist: false},
73364                     {name: 'allowDrop', type: 'boolean', defaultValue: true, persist: false},
73365                     {name: 'allowDrag', type: 'boolean', defaultValue: true, persist: false},
73366                     {name: 'loaded',    type: 'boolean', defaultValue: false, persist: false},
73367                     {name: 'loading',   type: 'boolean', defaultValue: false, persist: false},
73368                     {name: 'href',      type: 'string',  defaultValue: null, persist: false},
73369                     {name: 'hrefTarget',type: 'string',  defaultValue: null, persist: false},
73370                     {name: 'qtip',      type: 'string',  defaultValue: null, persist: false},
73371                     {name: 'qtitle',    type: 'string',  defaultValue: null, persist: false}
73372                 ]);
73373
73374                 jln = newFields.length;
73375                 // Set default values to all instances already out there
73376                 for (i = 0; i < iln; i++) {
73377                     instance = instances[i];
73378                     for (j = 0; j < jln; j++) {
73379                         newField = newFields[j];
73380                         if (instance.get(newField.name) === undefined) {
73381                             instance.data[newField.name] = newField.defaultValue;
73382                         }
73383                     }
73384                 }
73385             }
73386             
73387             Ext.applyIf(record, {
73388                 firstChild: null,
73389                 lastChild: null,
73390                 parentNode: null,
73391                 previousSibling: null,
73392                 nextSibling: null,
73393                 childNodes: []
73394             });
73395             // Commit any fields so the record doesn't show as dirty initially
73396             record.commit(true);
73397             
73398             record.enableBubble([
73399                 /**
73400                  * @event append
73401                  * Fires when a new child node is appended
73402                  * @param {Node} this This node
73403                  * @param {Node} node The newly appended node
73404                  * @param {Number} index The index of the newly appended node
73405                  */
73406                 "append",
73407
73408                 /**
73409                  * @event remove
73410                  * Fires when a child node is removed
73411                  * @param {Node} this This node
73412                  * @param {Node} node The removed node
73413                  */
73414                 "remove",
73415
73416                 /**
73417                  * @event move
73418                  * Fires when this node is moved to a new location in the tree
73419                  * @param {Node} this This node
73420                  * @param {Node} oldParent The old parent of this node
73421                  * @param {Node} newParent The new parent of this node
73422                  * @param {Number} index The index it was moved to
73423                  */
73424                 "move",
73425
73426                 /**
73427                  * @event insert
73428                  * Fires when a new child node is inserted.
73429                  * @param {Node} this This node
73430                  * @param {Node} node The child node inserted
73431                  * @param {Node} refNode The child node the node was inserted before
73432                  */
73433                 "insert",
73434
73435                 /**
73436                  * @event beforeappend
73437                  * Fires before a new child is appended, return false to cancel the append.
73438                  * @param {Node} this This node
73439                  * @param {Node} node The child node to be appended
73440                  */
73441                 "beforeappend",
73442
73443                 /**
73444                  * @event beforeremove
73445                  * Fires before a child is removed, return false to cancel the remove.
73446                  * @param {Node} this This node
73447                  * @param {Node} node The child node to be removed
73448                  */
73449                 "beforeremove",
73450
73451                 /**
73452                  * @event beforemove
73453                  * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
73454                  * @param {Node} this This node
73455                  * @param {Node} oldParent The parent of this node
73456                  * @param {Node} newParent The new parent this node is moving to
73457                  * @param {Number} index The index it is being moved to
73458                  */
73459                 "beforemove",
73460
73461                  /**
73462                   * @event beforeinsert
73463                   * Fires before a new child is inserted, return false to cancel the insert.
73464                   * @param {Node} this This node
73465                   * @param {Node} node The child node to be inserted
73466                   * @param {Node} refNode The child node the node is being inserted before
73467                   */
73468                 "beforeinsert",
73469                 
73470                 /**
73471                  * @event expand
73472                  * Fires when this node is expanded.
73473                  * @param {Node} this The expanding node
73474                  */
73475                 "expand",
73476                 
73477                 /**
73478                  * @event collapse
73479                  * Fires when this node is collapsed.
73480                  * @param {Node} this The collapsing node
73481                  */
73482                 "collapse",
73483                 
73484                 /**
73485                  * @event beforeexpand
73486                  * Fires before this node is expanded.
73487                  * @param {Node} this The expanding node
73488                  */
73489                 "beforeexpand",
73490                 
73491                 /**
73492                  * @event beforecollapse
73493                  * Fires before this node is collapsed.
73494                  * @param {Node} this The collapsing node
73495                  */
73496                 "beforecollapse",
73497                 
73498                 /**
73499                  * @event beforecollapse
73500                  * Fires before this node is collapsed.
73501                  * @param {Node} this The collapsing node
73502                  */
73503                 "sort"
73504             ]);
73505             
73506             return record;
73507         },
73508         
73509         applyFields: function(modelClass, addFields) {
73510             var modelPrototype = modelClass.prototype,
73511                 fields = modelPrototype.fields,
73512                 keys = fields.keys,
73513                 ln = addFields.length,
73514                 addField, i, name,
73515                 newFields = [];
73516                 
73517             for (i = 0; i < ln; i++) {
73518                 addField = addFields[i];
73519                 if (!Ext.Array.contains(keys, addField.name)) {
73520                     addField = Ext.create('data.field', addField);
73521                     
73522                     newFields.push(addField);
73523                     fields.add(addField);
73524                 }
73525             }
73526             
73527             return newFields;
73528         },
73529         
73530         getPrototypeBody: function() {
73531             return {
73532                 isNode: true,
73533
73534                 /**
73535                  * Ensures that the passed object is an instance of a Record with the NodeInterface applied
73536                  * @return {Boolean}
73537                  */
73538                 createNode: function(node) {
73539                     if (Ext.isObject(node) && !node.isModel) {
73540                         node = Ext.ModelManager.create(node, this.modelName);
73541                     }
73542                     // Make sure the node implements the node interface
73543                     return Ext.data.NodeInterface.decorate(node);
73544                 },
73545                 
73546                 /**
73547                  * Returns true if this node is a leaf
73548                  * @return {Boolean}
73549                  */
73550                 isLeaf : function() {
73551                     return this.get('leaf') === true;
73552                 },
73553
73554                 /**
73555                  * Sets the first child of this node
73556                  * @private
73557                  * @param {Ext.data.NodeInterface} node
73558                  */
73559                 setFirstChild : function(node) {
73560                     this.firstChild = node;
73561                 },
73562
73563                 /**
73564                  * Sets the last child of this node
73565                  * @private
73566                  * @param {Ext.data.NodeInterface} node
73567                  */
73568                 setLastChild : function(node) {
73569                     this.lastChild = node;
73570                 },
73571
73572                 /**
73573                  * Updates general data of this node like isFirst, isLast, depth. This
73574                  * method is internally called after a node is moved. This shouldn't
73575                  * have to be called by the developer unless they are creating custom
73576                  * Tree plugins.
73577                  * @return {Boolean}
73578                  */
73579                 updateInfo: function(silent) {
73580                     var me = this,
73581                         isRoot = me.isRoot(),
73582                         parentNode = me.parentNode,
73583                         isFirst = (!parentNode ? true : parentNode.firstChild == me),
73584                         isLast = (!parentNode ? true : parentNode.lastChild == me),
73585                         depth = 0,
73586                         parent = me,
73587                         children = me.childNodes,
73588                         len = children.length,
73589                         i = 0;
73590
73591                     while (parent.parentNode) {
73592                         ++depth;
73593                         parent = parent.parentNode;
73594                     }                                            
73595                     
73596                     me.beginEdit();
73597                     me.set({
73598                         isFirst: isFirst,
73599                         isLast: isLast,
73600                         depth: depth,
73601                         index: parentNode ? parentNode.indexOf(me) : 0,
73602                         parentId: parentNode ? parentNode.getId() : null
73603                     });
73604                     me.endEdit(silent);
73605                     if (silent) {
73606                         me.commit();
73607                     }
73608                     
73609                     for (i = 0; i < len; i++) {
73610                         children[i].updateInfo(silent);
73611                     }
73612                 },
73613
73614                 /**
73615                  * Returns true if this node is the last child of its parent
73616                  * @return {Boolean}
73617                  */
73618                 isLast : function() {
73619                    return this.get('isLast');
73620                 },
73621
73622                 /**
73623                  * Returns true if this node is the first child of its parent
73624                  * @return {Boolean}
73625                  */
73626                 isFirst : function() {
73627                    return this.get('isFirst');
73628                 },
73629
73630                 /**
73631                  * Returns true if this node has one or more child nodes, else false.
73632                  * @return {Boolean}
73633                  */
73634                 hasChildNodes : function() {
73635                     return !this.isLeaf() && this.childNodes.length > 0;
73636                 },
73637
73638                 /**
73639                  * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
73640                  * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
73641                  * @return {Boolean}
73642                  */
73643                 isExpandable : function() {
73644                     return this.get('expandable') || this.hasChildNodes();
73645                 },
73646
73647                 /**
73648                  * <p>Insert node(s) as the last child node of this node.</p>
73649                  * <p>If the node was previously a child node of another parent node, it will be removed from that node first.</p>
73650                  * @param {Node/Array} node The node or Array of nodes to append
73651                  * @return {Node} The appended node if single append, or null if an array was passed
73652                  */
73653                 appendChild : function(node, suppressEvents, suppressNodeUpdate) {
73654                     var me = this,
73655                         i, ln,
73656                         index,
73657                         oldParent,
73658                         ps;
73659
73660                     // if passed an array or multiple args do them one by one
73661                     if (Ext.isArray(node)) {
73662                         for (i = 0, ln = node.length; i < ln; i++) {
73663                             me.appendChild(node[i]);
73664                         }
73665                     } else {
73666                         // Make sure it is a record
73667                         node = me.createNode(node);
73668                         
73669                         if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
73670                             return false;                         
73671                         }
73672
73673                         index = me.childNodes.length;
73674                         oldParent = node.parentNode;
73675
73676                         // it's a move, make sure we move it cleanly
73677                         if (oldParent) {
73678                             if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
73679                                 return false;
73680                             }
73681                             oldParent.removeChild(node, null, false, true);
73682                         }
73683
73684                         index = me.childNodes.length;
73685                         if (index === 0) {
73686                             me.setFirstChild(node);
73687                         }
73688
73689                         me.childNodes.push(node);
73690                         node.parentNode = me;
73691                         node.nextSibling = null;
73692
73693                         me.setLastChild(node);
73694                                                 
73695                         ps = me.childNodes[index - 1];
73696                         if (ps) {
73697                             node.previousSibling = ps;
73698                             ps.nextSibling = node;
73699                             ps.updateInfo(suppressNodeUpdate);
73700                         } else {
73701                             node.previousSibling = null;
73702                         }
73703
73704                         node.updateInfo(suppressNodeUpdate);
73705                         
73706                         // As soon as we append a child to this node, we are loaded
73707                         if (!me.isLoaded()) {
73708                             me.set('loaded', true);                            
73709                         }
73710                         // If this node didnt have any childnodes before, update myself
73711                         else if (me.childNodes.length === 1) {
73712                             me.set('loaded', me.isLoaded());
73713                         }
73714                         
73715                         if (suppressEvents !== true) {
73716                             me.fireEvent("append", me, node, index);
73717
73718                             if (oldParent) {
73719                                 node.fireEvent("move", node, oldParent, me, index);
73720                             }                            
73721                         }
73722
73723                         return node;
73724                     }
73725                 },
73726                 
73727                 /**
73728                  * Returns the bubble target for this node
73729                  * @private
73730                  * @return {Object} The bubble target
73731                  */
73732                 getBubbleTarget: function() {
73733                     return this.parentNode;
73734                 },
73735
73736                 /**
73737                  * Removes a child node from this node.
73738                  * @param {Node} node The node to remove
73739                  * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
73740                  * @return {Node} The removed node
73741                  */
73742                 removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
73743                     var me = this,
73744                         index = me.indexOf(node);
73745                     
73746                     if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
73747                         return false;
73748                     }
73749
73750                     // remove it from childNodes collection
73751                     me.childNodes.splice(index, 1);
73752
73753                     // update child refs
73754                     if (me.firstChild == node) {
73755                         me.setFirstChild(node.nextSibling);
73756                     }
73757                     if (me.lastChild == node) {
73758                         me.setLastChild(node.previousSibling);
73759                     }
73760                     
73761                     // update siblings
73762                     if (node.previousSibling) {
73763                         node.previousSibling.nextSibling = node.nextSibling;
73764                         node.previousSibling.updateInfo(suppressNodeUpdate);
73765                     }
73766                     if (node.nextSibling) {
73767                         node.nextSibling.previousSibling = node.previousSibling;
73768                         node.nextSibling.updateInfo(suppressNodeUpdate);
73769                     }
73770
73771                     if (suppressEvents !== true) {
73772                         me.fireEvent("remove", me, node);
73773                     }
73774                     
73775                     
73776                     // If this node suddenly doesnt have childnodes anymore, update myself
73777                     if (!me.childNodes.length) {
73778                         me.set('loaded', me.isLoaded());
73779                     }
73780                     
73781                     if (destroy) {
73782                         node.destroy(true);
73783                     } else {
73784                         node.clear();
73785                     }
73786
73787                     return node;
73788                 },
73789
73790                 /**
73791                  * Creates a copy (clone) of this Node.
73792                  * @param {String} id (optional) A new id, defaults to this Node's id. See <code>{@link #id}</code>.
73793                  * @param {Boolean} deep (optional) <p>If passed as <code>true</code>, all child Nodes are recursively copied into the new Node.</p>
73794                  * <p>If omitted or false, the copy will have no child Nodes.</p>
73795                  * @return {Node} A copy of this Node.
73796                  */
73797                 copy: function(newId, deep) {
73798                     var me = this,
73799                         result = me.callOverridden(arguments),
73800                         len = me.childNodes ? me.childNodes.length : 0,
73801                         i;
73802
73803                     // Move child nodes across to the copy if required
73804                     if (deep) {
73805                         for (i = 0; i < len; i++) {
73806                             result.appendChild(me.childNodes[i].copy(true));
73807                         }
73808                     }
73809                     return result;
73810                 },
73811
73812                 /**
73813                  * Clear the node.
73814                  * @private
73815                  * @param {Boolean} destroy True to destroy the node.
73816                  */
73817                 clear : function(destroy) {
73818                     var me = this;
73819                     
73820                     // clear any references from the node
73821                     me.parentNode = me.previousSibling = me.nextSibling = null;
73822                     if (destroy) {
73823                         me.firstChild = me.lastChild = null;
73824                     }
73825                 },
73826
73827                 /**
73828                  * Destroys the node.
73829                  */
73830                 destroy : function(silent) {
73831                     /*
73832                      * Silent is to be used in a number of cases
73833                      * 1) When setRoot is called.
73834                      * 2) When destroy on the tree is called
73835                      * 3) For destroying child nodes on a node
73836                      */
73837                     var me = this,
73838                         options = me.destroyOptions;
73839                     
73840                     if (silent === true) {
73841                         me.clear(true);
73842                         Ext.each(me.childNodes, function(n) {
73843                             n.destroy(true);
73844                         });
73845                         me.childNodes = null;
73846                         delete me.destroyOptions;
73847                         me.callOverridden([options]);
73848                     } else {
73849                         me.destroyOptions = silent;
73850                         // overridden method will be called, since remove will end up calling destroy(true);
73851                         me.remove(true);
73852                     }
73853                 },
73854
73855                 /**
73856                  * Inserts the first node before the second node in this nodes childNodes collection.
73857                  * @param {Node} node The node to insert
73858                  * @param {Node} refNode The node to insert before (if null the node is appended)
73859                  * @return {Node} The inserted node
73860                  */
73861                 insertBefore : function(node, refNode, suppressEvents) {
73862                     var me = this,
73863                         index     = me.indexOf(refNode),
73864                         oldParent = node.parentNode,
73865                         refIndex  = index,
73866                         ps;
73867                     
73868                     if (!refNode) { // like standard Dom, refNode can be null for append
73869                         return me.appendChild(node);
73870                     }
73871                     
73872                     // nothing to do
73873                     if (node == refNode) {
73874                         return false;
73875                     }
73876
73877                     // Make sure it is a record with the NodeInterface
73878                     node = me.createNode(node);
73879                     
73880                     if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
73881                         return false;
73882                     }
73883                     
73884                     // when moving internally, indexes will change after remove
73885                     if (oldParent == me && me.indexOf(node) < index) {
73886                         refIndex--;
73887                     }
73888
73889                     // it's a move, make sure we move it cleanly
73890                     if (oldParent) {
73891                         if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
73892                             return false;
73893                         }
73894                         oldParent.removeChild(node);
73895                     }
73896
73897                     if (refIndex === 0) {
73898                         me.setFirstChild(node);
73899                     }
73900
73901                     me.childNodes.splice(refIndex, 0, node);
73902                     node.parentNode = me;
73903                     
73904                     node.nextSibling = refNode;
73905                     refNode.previousSibling = node;
73906                     
73907                     ps = me.childNodes[refIndex - 1];
73908                     if (ps) {
73909                         node.previousSibling = ps;
73910                         ps.nextSibling = node;
73911                         ps.updateInfo();
73912                     } else {
73913                         node.previousSibling = null;
73914                     }
73915                     
73916                     node.updateInfo();
73917                     
73918                     if (!me.isLoaded()) {
73919                         me.set('loaded', true);                            
73920                     }    
73921                     // If this node didnt have any childnodes before, update myself
73922                     else if (me.childNodes.length === 1) {
73923                         me.set('loaded', me.isLoaded());
73924                     }
73925
73926                     if (suppressEvents !== true) {
73927                         me.fireEvent("insert", me, node, refNode);
73928
73929                         if (oldParent) {
73930                             node.fireEvent("move", node, oldParent, me, refIndex, refNode);
73931                         }                        
73932                     }
73933
73934                     return node;
73935                 },
73936                 
73937                 /**
73938                  * Insert a node into this node
73939                  * @param {Number} index The zero-based index to insert the node at
73940                  * @param {Ext.data.Model} node The node to insert
73941                  * @return {Ext.data.Record} The record you just inserted
73942                  */    
73943                 insertChild: function(index, node) {
73944                     var sibling = this.childNodes[index];
73945                     if (sibling) {
73946                         return this.insertBefore(node, sibling);
73947                     }
73948                     else {
73949                         return this.appendChild(node);
73950                     }
73951                 },
73952
73953                 /**
73954                  * Removes this node from its parent
73955                  * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
73956                  * @return {Node} this
73957                  */
73958                 remove : function(destroy, suppressEvents) {
73959                     var parentNode = this.parentNode;
73960
73961                     if (parentNode) {
73962                         parentNode.removeChild(this, destroy, suppressEvents, true);
73963                     }
73964                     return this;
73965                 },
73966
73967                 /**
73968                  * Removes all child nodes from this node.
73969                  * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
73970                  * @return {Node} this
73971                  */
73972                 removeAll : function(destroy, suppressEvents) {
73973                     var cn = this.childNodes,
73974                         n;
73975
73976                     while ((n = cn[0])) {
73977                         this.removeChild(n, destroy, suppressEvents);
73978                     }
73979                     return this;
73980                 },
73981
73982                 /**
73983                  * Returns the child node at the specified index.
73984                  * @param {Number} index
73985                  * @return {Node}
73986                  */
73987                 getChildAt : function(index) {
73988                     return this.childNodes[index];
73989                 },
73990
73991                 /**
73992                  * Replaces one child node in this node with another.
73993                  * @param {Node} newChild The replacement node
73994                  * @param {Node} oldChild The node to replace
73995                  * @return {Node} The replaced node
73996                  */
73997                 replaceChild : function(newChild, oldChild, suppressEvents) {
73998                     var s = oldChild ? oldChild.nextSibling : null;
73999                     
74000                     this.removeChild(oldChild, suppressEvents);
74001                     this.insertBefore(newChild, s, suppressEvents);
74002                     return oldChild;
74003                 },
74004
74005                 /**
74006                  * Returns the index of a child node
74007                  * @param {Node} node
74008                  * @return {Number} The index of the node or -1 if it was not found
74009                  */
74010                 indexOf : function(child) {
74011                     return Ext.Array.indexOf(this.childNodes, child);
74012                 },
74013
74014                 /**
74015                  * Returns depth of this node (the root node has a depth of 0)
74016                  * @return {Number}
74017                  */
74018                 getDepth : function() {
74019                     return this.get('depth');
74020                 },
74021
74022                 /**
74023                  * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
74024                  * will be the args provided or the current node. If the function returns false at any point,
74025                  * the bubble is stopped.
74026                  * @param {Function} fn The function to call
74027                  * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
74028                  * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
74029                  */
74030                 bubble : function(fn, scope, args) {
74031                     var p = this;
74032                     while (p) {
74033                         if (fn.apply(scope || p, args || [p]) === false) {
74034                             break;
74035                         }
74036                         p = p.parentNode;
74037                     }
74038                 },
74039
74040                 cascade: function() {
74041                     if (Ext.isDefined(Ext.global.console)) {
74042                         Ext.global.console.warn('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');
74043                     }
74044                     return this.cascadeBy.apply(this, arguments);
74045                 },
74046
74047                 /**
74048                  * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
74049                  * will be the args provided or the current node. If the function returns false at any point,
74050                  * the cascade is stopped on that branch.
74051                  * @param {Function} fn The function to call
74052                  * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
74053                  * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
74054                  */
74055                 cascadeBy : function(fn, scope, args) {
74056                     if (fn.apply(scope || this, args || [this]) !== false) {
74057                         var childNodes = this.childNodes,
74058                             length     = childNodes.length,
74059                             i;
74060
74061                         for (i = 0; i < length; i++) {
74062                             childNodes[i].cascadeBy(fn, scope, args);
74063                         }
74064                     }
74065                 },
74066
74067                 /**
74068                  * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
74069                  * will be the args provided or the current node. If the function returns false at any point,
74070                  * the iteration stops.
74071                  * @param {Function} fn The function to call
74072                  * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node in the iteration.
74073                  * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
74074                  */
74075                 eachChild : function(fn, scope, args) {
74076                     var childNodes = this.childNodes,
74077                         length     = childNodes.length,
74078                         i;
74079
74080                     for (i = 0; i < length; i++) {
74081                         if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
74082                             break;
74083                         }
74084                     }
74085                 },
74086
74087                 /**
74088                  * Finds the first child that has the attribute with the specified value.
74089                  * @param {String} attribute The attribute name
74090                  * @param {Mixed} value The value to search for
74091                  * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
74092                  * @return {Node} The found child or null if none was found
74093                  */
74094                 findChild : function(attribute, value, deep) {
74095                     return this.findChildBy(function() {
74096                         return this.get(attribute) == value;
74097                     }, null, deep);
74098                 },
74099
74100                 /**
74101                  * Finds the first child by a custom function. The child matches if the function passed returns <code>true</code>.
74102                  * @param {Function} fn A function which must return <code>true</code> if the passed Node is the required Node.
74103                  * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Node being tested.
74104                  * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
74105                  * @return {Node} The found child or null if none was found
74106                  */
74107                 findChildBy : function(fn, scope, deep) {
74108                     var cs = this.childNodes,
74109                         len = cs.length,
74110                         i = 0, n, res;
74111
74112                     for (; i < len; i++) {
74113                         n = cs[i];
74114                         if (fn.call(scope || n, n) === true) {
74115                             return n;
74116                         }
74117                         else if (deep) {
74118                             res = n.findChildBy(fn, scope, deep);
74119                             if (res !== null) {
74120                                 return res;
74121                             }
74122                         }
74123                     }
74124
74125                     return null;
74126                 },
74127
74128                 /**
74129                  * Returns true if this node is an ancestor (at any point) of the passed node.
74130                  * @param {Node} node
74131                  * @return {Boolean}
74132                  */
74133                 contains : function(node) {
74134                     return node.isAncestor(this);
74135                 },
74136
74137                 /**
74138                  * Returns true if the passed node is an ancestor (at any point) of this node.
74139                  * @param {Node} node
74140                  * @return {Boolean}
74141                  */
74142                 isAncestor : function(node) {
74143                     var p = this.parentNode;
74144                     while (p) {
74145                         if (p == node) {
74146                             return true;
74147                         }
74148                         p = p.parentNode;
74149                     }
74150                     return false;
74151                 },
74152
74153                 /**
74154                  * Sorts this nodes children using the supplied sort function.
74155                  * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
74156                  * @param {Boolean} recursive Whether or not to apply this sort recursively
74157                  * @param {Boolean} suppressEvent Set to true to not fire a sort event.
74158                  */
74159                 sort : function(sortFn, recursive, suppressEvent) {
74160                     var cs  = this.childNodes,
74161                         ln = cs.length,
74162                         i, n;
74163                     
74164                     if (ln > 0) {
74165                         Ext.Array.sort(cs, sortFn);
74166                         for (i = 0; i < ln; i++) {
74167                             n = cs[i];
74168                             n.previousSibling = cs[i-1];
74169                             n.nextSibling = cs[i+1];
74170                         
74171                             if (i === 0) {
74172                                 this.setFirstChild(n);
74173                                 n.updateInfo();
74174                             }
74175                             if (i == ln - 1) {
74176                                 this.setLastChild(n);
74177                                 n.updateInfo();
74178                             }
74179                             if (recursive && !n.isLeaf()) {
74180                                 n.sort(sortFn, true, true);
74181                             }
74182                         }
74183                         
74184                         if (suppressEvent !== true) {
74185                             this.fireEvent('sort', this, cs);
74186                         }
74187                     }
74188                 },
74189                         
74190                 /**
74191                  * Returns true if this node is expaned
74192                  * @return {Boolean}
74193                  */        
74194                 isExpanded: function() {
74195                     return this.get('expanded');
74196                 },
74197                 
74198                 /**
74199                  * Returns true if this node is loaded
74200                  * @return {Boolean}
74201                  */ 
74202                 isLoaded: function() {
74203                     return this.get('loaded');
74204                 },
74205
74206                 /**
74207                  * Returns true if this node is loading
74208                  * @return {Boolean}
74209                  */ 
74210                 isLoading: function() {
74211                     return this.get('loading');
74212                 },
74213                                 
74214                 /**
74215                  * Returns true if this node is the root node
74216                  * @return {Boolean}
74217                  */ 
74218                 isRoot: function() {
74219                     return !this.parentNode;
74220                 },
74221                 
74222                 /**
74223                  * Returns true if this node is visible
74224                  * @return {Boolean}
74225                  */ 
74226                 isVisible: function() {
74227                     var parent = this.parentNode;
74228                     while (parent) {
74229                         if (!parent.isExpanded()) {
74230                             return false;
74231                         }
74232                         parent = parent.parentNode;
74233                     }
74234                     return true;
74235                 },
74236                 
74237                 /**
74238                  * Expand this node.
74239                  * @param {Function} recursive (Optional) True to recursively expand all the children
74240                  * @param {Function} callback (Optional) The function to execute once the expand completes
74241                  * @param {Object} scope (Optional) The scope to run the callback in
74242                  */
74243                 expand: function(recursive, callback, scope) {
74244                     var me = this;
74245
74246                     // all paths must call the callback (eventually) or things like
74247                     // selectPath fail
74248
74249                     // First we start by checking if this node is a parent
74250                     if (!me.isLeaf()) {
74251                         // Now we check if this record is already expanding or expanded
74252                         if (!me.isLoading() && !me.isExpanded()) {
74253                             // The TreeStore actually listens for the beforeexpand method and checks
74254                             // whether we have to asynchronously load the children from the server
74255                             // first. Thats why we pass a callback function to the event that the
74256                             // store can call once it has loaded and parsed all the children.
74257                             me.fireEvent('beforeexpand', me, function(records) {
74258                                 me.set('expanded', true); 
74259                                 me.fireEvent('expand', me, me.childNodes, false);
74260                                 
74261                                 // Call the expandChildren method if recursive was set to true 
74262                                 if (recursive) {
74263                                     me.expandChildren(true, callback, scope);
74264                                 }
74265                                 else {
74266                                     Ext.callback(callback, scope || me, [me.childNodes]);                                
74267                                 }
74268                             }, me);                            
74269                         }
74270                         // If it is is already expanded but we want to recursively expand then call expandChildren
74271                         else if (recursive) {
74272                             me.expandChildren(true, callback, scope);
74273                         }
74274                         else {
74275                             Ext.callback(callback, scope || me, [me.childNodes]);
74276                         }
74277
74278                         // TODO - if the node isLoading, we probably need to defer the
74279                         // callback until it is loaded (e.g., selectPath would need us
74280                         // to not make the callback until the childNodes exist).
74281                     }
74282                     // If it's not then we fire the callback right away
74283                     else {
74284                         Ext.callback(callback, scope || me); // leaf = no childNodes
74285                     }
74286                 },
74287                 
74288                 /**
74289                  * Expand all the children of this node.
74290                  * @param {Function} recursive (Optional) True to recursively expand all the children
74291                  * @param {Function} callback (Optional) The function to execute once all the children are expanded
74292                  * @param {Object} scope (Optional) The scope to run the callback in
74293                  */
74294                 expandChildren: function(recursive, callback, scope) {
74295                     var me = this,
74296                         i = 0,
74297                         nodes = me.childNodes,
74298                         ln = nodes.length,
74299                         node,
74300                         expanding = 0;
74301
74302                     for (; i < ln; ++i) {
74303                         node = nodes[i];
74304                         if (!node.isLeaf() && !node.isExpanded()) {
74305                             expanding++;
74306                             nodes[i].expand(recursive, function () {
74307                                 expanding--;
74308                                 if (callback && !expanding) {
74309                                     Ext.callback(callback, scope || me, me.childNodes); 
74310                                 }
74311                             });                            
74312                         }
74313                     }
74314                     
74315                     if (!expanding && callback) {
74316                         Ext.callback(callback, scope || me, me.childNodes);
74317                     }
74318                 },
74319
74320                 /**
74321                  * Collapse this node.
74322                  * @param {Function} recursive (Optional) True to recursively collapse all the children
74323                  * @param {Function} callback (Optional) The function to execute once the collapse completes
74324                  * @param {Object} scope (Optional) The scope to run the callback in
74325                  */
74326                 collapse: function(recursive, callback, scope) {
74327                     var me = this;
74328
74329                     // First we start by checking if this node is a parent
74330                     if (!me.isLeaf()) {
74331                         // Now we check if this record is already collapsing or collapsed
74332                         if (!me.collapsing && me.isExpanded()) {
74333                             me.fireEvent('beforecollapse', me, function(records) {
74334                                 me.set('expanded', false); 
74335                                 me.fireEvent('collapse', me, me.childNodes, false);
74336                                 
74337                                 // Call the collapseChildren method if recursive was set to true 
74338                                 if (recursive) {
74339                                     me.collapseChildren(true, callback, scope);
74340                                 }
74341                                 else {
74342                                     Ext.callback(callback, scope || me, [me.childNodes]);                                
74343                                 }
74344                             }, me);                            
74345                         }
74346                         // If it is is already collapsed but we want to recursively collapse then call collapseChildren
74347                         else if (recursive) {
74348                             me.collapseChildren(true, callback, scope);
74349                         }
74350                     }
74351                     // If it's not then we fire the callback right away
74352                     else {
74353                         Ext.callback(callback, scope || me, me.childNodes); 
74354                     }
74355                 },
74356                 
74357                 /**
74358                  * Collapse all the children of this node.
74359                  * @param {Function} recursive (Optional) True to recursively collapse all the children
74360                  * @param {Function} callback (Optional) The function to execute once all the children are collapsed
74361                  * @param {Object} scope (Optional) The scope to run the callback in
74362                  */
74363                 collapseChildren: function(recursive, callback, scope) {
74364                     var me = this,
74365                         i = 0,
74366                         nodes = me.childNodes,
74367                         ln = nodes.length,
74368                         node,
74369                         collapsing = 0;
74370
74371                     for (; i < ln; ++i) {
74372                         node = nodes[i];
74373                         if (!node.isLeaf() && node.isExpanded()) {
74374                             collapsing++;
74375                             nodes[i].collapse(recursive, function () {
74376                                 collapsing--;
74377                                 if (callback && !collapsing) {
74378                                     Ext.callback(callback, scope || me, me.childNodes); 
74379                                 }
74380                             });                            
74381                         }
74382                     }
74383                     
74384                     if (!collapsing && callback) {
74385                         Ext.callback(callback, scope || me, me.childNodes);
74386                     }
74387                 }
74388             };
74389         }
74390     }
74391 });
74392 /**
74393  * @class Ext.data.NodeStore
74394  * @extends Ext.data.AbstractStore
74395  * Node Store
74396  * @ignore
74397  */
74398 Ext.define('Ext.data.NodeStore', {
74399     extend: 'Ext.data.Store',
74400     alias: 'store.node',
74401     requires: ['Ext.data.NodeInterface'],
74402     
74403     /**
74404      * @cfg {Ext.data.Record} node The Record you want to bind this Store to. Note that
74405      * this record will be decorated with the Ext.data.NodeInterface if this is not the
74406      * case yet.
74407      */
74408     node: null,
74409     
74410     /**
74411      * @cfg {Boolean} recursive Set this to true if you want this NodeStore to represent
74412      * all the descendents of the node in its flat data collection. This is useful for
74413      * rendering a tree structure to a DataView and is being used internally by
74414      * the TreeView. Any records that are moved, removed, inserted or appended to the
74415      * node at any depth below the node this store is bound to will be automatically
74416      * updated in this Store's internal flat data structure.
74417      */
74418     recursive: false,
74419     
74420     /** 
74421      * @cfg {Boolean} rootVisible <tt>false</tt> to not include the root node in this Stores collection (defaults to <tt>true</tt>)
74422      */    
74423     rootVisible: false,
74424     
74425     constructor: function(config) {
74426         var me = this,
74427             node;
74428             
74429         config = config || {};
74430         Ext.apply(me, config);
74431         
74432         if (Ext.isDefined(me.proxy)) {
74433             Ext.Error.raise("A NodeStore cannot be bound to a proxy. Instead bind it to a record " +
74434                             "decorated with the NodeInterface by setting the node config.");
74435         }
74436
74437         config.proxy = {type: 'proxy'};
74438         me.callParent([config]);
74439
74440         me.addEvents('expand', 'collapse', 'beforeexpand', 'beforecollapse');
74441         
74442         node = me.node;
74443         if (node) {
74444             me.node = null;
74445             me.setNode(node);
74446         }
74447     },
74448     
74449     setNode: function(node) {
74450         var me = this;
74451         
74452         if (me.node && me.node != node) {
74453             // We want to unbind our listeners on the old node
74454             me.mun(me.node, {
74455                 expand: me.onNodeExpand,
74456                 collapse: me.onNodeCollapse,
74457                 append: me.onNodeAppend,
74458                 insert: me.onNodeInsert,
74459                 remove: me.onNodeRemove,
74460                 sort: me.onNodeSort,
74461                 scope: me
74462             });
74463             me.node = null;
74464         }
74465         
74466         if (node) {
74467             Ext.data.NodeInterface.decorate(node);
74468             me.removeAll();
74469             if (me.rootVisible) {
74470                 me.add(node);
74471             }
74472             me.mon(node, {
74473                 expand: me.onNodeExpand,
74474                 collapse: me.onNodeCollapse,
74475                 append: me.onNodeAppend,
74476                 insert: me.onNodeInsert,
74477                 remove: me.onNodeRemove,
74478                 sort: me.onNodeSort,
74479                 scope: me
74480             });
74481             me.node = node;
74482             if (node.isExpanded() && node.isLoaded()) {
74483                 me.onNodeExpand(node, node.childNodes, true);
74484             }
74485         }
74486     },
74487     
74488     onNodeSort: function(node, childNodes) {
74489         var me = this;
74490         
74491         if ((me.indexOf(node) !== -1 || (node === me.node && !me.rootVisible) && node.isExpanded())) {
74492             me.onNodeCollapse(node, childNodes, true);
74493             me.onNodeExpand(node, childNodes, true);
74494         }
74495     },
74496     
74497     onNodeExpand: function(parent, records, suppressEvent) {
74498         var me = this,
74499             insertIndex = me.indexOf(parent) + 1,
74500             ln = records ? records.length : 0,
74501             i, record;
74502             
74503         if (!me.recursive && parent !== me.node) {
74504             return;
74505         }
74506         
74507         if (!me.isVisible(parent)) {
74508             return;
74509         }
74510
74511         if (!suppressEvent && me.fireEvent('beforeexpand', parent, records, insertIndex) === false) {
74512             return;
74513         }
74514         
74515         if (ln) {
74516             me.insert(insertIndex, records);
74517             for (i = 0; i < ln; i++) {
74518                 record = records[i];
74519                 if (record.isExpanded()) {
74520                     if (record.isLoaded()) {
74521                         // Take a shortcut                        
74522                         me.onNodeExpand(record, record.childNodes, true);
74523                     }
74524                     else {
74525                         record.set('expanded', false);
74526                         record.expand();
74527                     }
74528                 }
74529             }
74530         }
74531
74532         if (!suppressEvent) {
74533             me.fireEvent('expand', parent, records);
74534         }
74535     },
74536
74537     onNodeCollapse: function(parent, records, suppressEvent) {
74538         var me = this,
74539             ln = records.length,
74540             collapseIndex = me.indexOf(parent) + 1,
74541             i, record;
74542             
74543         if (!me.recursive && parent !== me.node) {
74544             return;
74545         }
74546         
74547         if (!suppressEvent && me.fireEvent('beforecollapse', parent, records, collapseIndex) === false) {
74548             return;
74549         }
74550
74551         for (i = 0; i < ln; i++) {
74552             record = records[i];
74553             me.remove(record);
74554             if (record.isExpanded()) {
74555                 me.onNodeCollapse(record, record.childNodes, true);
74556             }
74557         }
74558         
74559         if (!suppressEvent) {
74560             me.fireEvent('collapse', parent, records, collapseIndex);
74561         }
74562     },
74563     
74564     onNodeAppend: function(parent, node, index) {
74565         var me = this,
74566             refNode, sibling;
74567
74568         if (me.isVisible(node)) {
74569             if (index === 0) {
74570                 refNode = parent;
74571             } else {
74572                 sibling = node.previousSibling;
74573                 while (sibling.isExpanded() && sibling.lastChild) {
74574                     sibling = sibling.lastChild;
74575                 }
74576                 refNode = sibling;
74577             }
74578             me.insert(me.indexOf(refNode) + 1, node);
74579             if (!node.isLeaf() && node.isExpanded()) {
74580                 if (node.isLoaded()) {
74581                     // Take a shortcut                        
74582                     me.onNodeExpand(node, node.childNodes, true);
74583                 }
74584                 else {
74585                     node.set('expanded', false);
74586                     node.expand();
74587                 }
74588             }
74589         } 
74590     },
74591     
74592     onNodeInsert: function(parent, node, refNode) {
74593         var me = this,
74594             index = this.indexOf(refNode);
74595             
74596         if (index != -1 && me.isVisible(node)) {
74597             me.insert(index, node);
74598             if (!node.isLeaf() && node.isExpanded()) {
74599                 if (node.isLoaded()) {
74600                     // Take a shortcut                        
74601                     me.onNodeExpand(node, node.childNodes, true);
74602                 }
74603                 else {
74604                     node.set('expanded', false);
74605                     node.expand();
74606                 }
74607             }
74608         }
74609     },
74610     
74611     onNodeRemove: function(parent, node, index) {
74612         var me = this;
74613         if (me.indexOf(node) != -1) {
74614             if (!node.isLeaf() && node.isExpanded()) {
74615                 me.onNodeCollapse(node, node.childNodes, true);
74616             }            
74617             me.remove(node);
74618         }
74619     },
74620     
74621     isVisible: function(node) {
74622         var parent = node.parentNode;
74623         while (parent) {
74624             if (parent === this.node && !this.rootVisible && parent.isExpanded()) {
74625                 return true;
74626             }
74627             
74628             if (this.indexOf(parent) === -1 || !parent.isExpanded()) {
74629                 return false;
74630             }
74631             
74632             parent = parent.parentNode;
74633         }
74634         return true;
74635     }
74636 });
74637 /**
74638  * @author Ed Spencer
74639  * @class Ext.data.Request
74640  * @extends Object
74641  * 
74642  * <p>Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
74643  * All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
74644  * it does not contain any actual logic or perform the request itself.</p>
74645  * 
74646  * @constructor
74647  * @param {Object} config Optional config object
74648  */
74649 Ext.define('Ext.data.Request', {
74650     /**
74651      * @cfg {String} action The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'
74652      */
74653     action: undefined,
74654     
74655     /**
74656      * @cfg {Object} params HTTP request params. The Proxy and its Writer have access to and can modify this object.
74657      */
74658     params: undefined,
74659     
74660     /**
74661      * @cfg {String} method The HTTP method to use on this Request (defaults to 'GET'). Should be one of 'GET', 'POST', 'PUT' or 'DELETE'
74662      */
74663     method: 'GET',
74664     
74665     /**
74666      * @cfg {String} url The url to access on this Request
74667      */
74668     url: undefined,
74669
74670     constructor: function(config) {
74671         Ext.apply(this, config);
74672     }
74673 });
74674 /**
74675  * @class Ext.data.Tree
74676  * 
74677  * This class is used as a container for a series of nodes. The nodes themselves maintain
74678  * the relationship between parent/child. The tree itself acts as a manager. It gives functionality
74679  * to retrieve a node by its identifier: {@link #getNodeById}. 
74680  *
74681  * The tree also relays events from any of it's child nodes, allowing them to be handled in a 
74682  * centralized fashion. In general this class is not used directly, rather used internally 
74683  * by other parts of the framework.
74684  *
74685  * @constructor
74686  * @param {Node} root (optional) The root node
74687  */
74688 Ext.define('Ext.data.Tree', {
74689     alias: 'data.tree',
74690     
74691     mixins: {
74692         observable: "Ext.util.Observable"
74693     },
74694
74695     /**
74696      * The root node for this tree
74697      * @type Node
74698      */
74699     root: null,
74700         
74701     constructor: function(root) {
74702         var me = this;
74703         
74704         me.nodeHash = {};
74705
74706         me.mixins.observable.constructor.call(me);
74707                         
74708         if (root) {
74709             me.setRootNode(root);
74710         }
74711     },
74712
74713     /**
74714      * Returns the root node for this tree.
74715      * @return {Ext.data.NodeInterface}
74716      */
74717     getRootNode : function() {
74718         return this.root;
74719     },
74720
74721     /**
74722      * Sets the root node for this tree.
74723      * @param {Ext.data.NodeInterface} node
74724      * @return {Ext.data.NodeInterface} The root node
74725      */
74726     setRootNode : function(node) {
74727         var me = this;
74728         
74729         me.root = node;
74730         Ext.data.NodeInterface.decorate(node);
74731         
74732         if (me.fireEvent('beforeappend', null, node) !== false) {
74733             node.set('root', true);
74734             node.updateInfo();
74735             
74736             me.relayEvents(node, [
74737                 /**
74738                  * @event append
74739                  * Fires when a new child node is appended to a node in this tree.
74740                  * @param {Tree} tree The owner tree
74741                  * @param {Node} parent The parent node
74742                  * @param {Node} node The newly appended node
74743                  * @param {Number} index The index of the newly appended node
74744                  */
74745                 "append",
74746
74747                 /**
74748                  * @event remove
74749                  * Fires when a child node is removed from a node in this tree.
74750                  * @param {Tree} tree The owner tree
74751                  * @param {Node} parent The parent node
74752                  * @param {Node} node The child node removed
74753                  */
74754                 "remove",
74755
74756                 /**
74757                  * @event move
74758                  * Fires when a node is moved to a new location in the tree
74759                  * @param {Tree} tree The owner tree
74760                  * @param {Node} node The node moved
74761                  * @param {Node} oldParent The old parent of this node
74762                  * @param {Node} newParent The new parent of this node
74763                  * @param {Number} index The index it was moved to
74764                  */
74765                 "move",
74766
74767                 /**
74768                  * @event insert
74769                  * Fires when a new child node is inserted in a node in this tree.
74770                  * @param {Tree} tree The owner tree
74771                  * @param {Node} parent The parent node
74772                  * @param {Node} node The child node inserted
74773                  * @param {Node} refNode The child node the node was inserted before
74774                  */
74775                 "insert",
74776
74777                 /**
74778                  * @event beforeappend
74779                  * Fires before a new child is appended to a node in this tree, return false to cancel the append.
74780                  * @param {Tree} tree The owner tree
74781                  * @param {Node} parent The parent node
74782                  * @param {Node} node The child node to be appended
74783                  */
74784                 "beforeappend",
74785
74786                 /**
74787                  * @event beforeremove
74788                  * Fires before a child is removed from a node in this tree, return false to cancel the remove.
74789                  * @param {Tree} tree The owner tree
74790                  * @param {Node} parent The parent node
74791                  * @param {Node} node The child node to be removed
74792                  */
74793                 "beforeremove",
74794
74795                 /**
74796                  * @event beforemove
74797                  * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
74798                  * @param {Tree} tree The owner tree
74799                  * @param {Node} node The node being moved
74800                  * @param {Node} oldParent The parent of the node
74801                  * @param {Node} newParent The new parent the node is moving to
74802                  * @param {Number} index The index it is being moved to
74803                  */
74804                 "beforemove",
74805
74806                 /**
74807                  * @event beforeinsert
74808                  * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
74809                  * @param {Tree} tree The owner tree
74810                  * @param {Node} parent The parent node
74811                  * @param {Node} node The child node to be inserted
74812                  * @param {Node} refNode The child node the node is being inserted before
74813                  */
74814                 "beforeinsert",
74815
74816                  /**
74817                   * @event expand
74818                   * Fires when this node is expanded.
74819                   * @param {Node} this The expanding node
74820                   */
74821                  "expand",
74822
74823                  /**
74824                   * @event collapse
74825                   * Fires when this node is collapsed.
74826                   * @param {Node} this The collapsing node
74827                   */
74828                  "collapse",
74829
74830                  /**
74831                   * @event beforeexpand
74832                   * Fires before this node is expanded.
74833                   * @param {Node} this The expanding node
74834                   */
74835                  "beforeexpand",
74836
74837                  /**
74838                   * @event beforecollapse
74839                   * Fires before this node is collapsed.
74840                   * @param {Node} this The collapsing node
74841                   */
74842                  "beforecollapse" ,
74843
74844                  /**
74845                   * @event rootchange
74846                   * Fires whenever the root node is changed in the tree.
74847                   * @param {Ext.data.Model} root The new root
74848                   */
74849                  "rootchange"
74850             ]);
74851             
74852             node.on({
74853                 scope: me,
74854                 insert: me.onNodeInsert,
74855                 append: me.onNodeAppend,
74856                 remove: me.onNodeRemove
74857             });
74858
74859             me.registerNode(node);        
74860             me.fireEvent('append', null, node);
74861             me.fireEvent('rootchange', node);
74862         }
74863             
74864         return node;
74865     },
74866     
74867     /**
74868      * Flattens all the nodes in the tree into an array.
74869      * @private
74870      * @return {Array} The flattened nodes.
74871      */
74872     flatten: function(){
74873         var nodes = [],
74874             hash = this.nodeHash,
74875             key;
74876             
74877         for (key in hash) {
74878             if (hash.hasOwnProperty(key)) {
74879                 nodes.push(hash[key]);
74880             }
74881         }
74882         return nodes;
74883     },
74884     
74885     /**
74886      * Fired when a node is inserted into the root or one of it's children
74887      * @private
74888      * @param {Ext.data.NodeInterface} parent The parent node
74889      * @param {Ext.data.NodeInterface} node The inserted node
74890      */
74891     onNodeInsert: function(parent, node) {
74892         this.registerNode(node);
74893     },
74894     
74895     /**
74896      * Fired when a node is appended into the root or one of it's children
74897      * @private
74898      * @param {Ext.data.NodeInterface} parent The parent node
74899      * @param {Ext.data.NodeInterface} node The appended node
74900      */
74901     onNodeAppend: function(parent, node) {
74902         this.registerNode(node);
74903     },
74904     
74905     /**
74906      * Fired when a node is removed from the root or one of it's children
74907      * @private
74908      * @param {Ext.data.NodeInterface} parent The parent node
74909      * @param {Ext.data.NodeInterface} node The removed node
74910      */
74911     onNodeRemove: function(parent, node) {
74912         this.unregisterNode(node);
74913     },
74914
74915     /**
74916      * Gets a node in this tree by its id.
74917      * @param {String} id
74918      * @return {Ext.data.NodeInterface} The match node.
74919      */
74920     getNodeById : function(id) {
74921         return this.nodeHash[id];
74922     },
74923
74924     /**
74925      * Registers a node with the tree
74926      * @private
74927      * @param {Ext.data.NodeInterface} The node to register
74928      */
74929     registerNode : function(node) {
74930         this.nodeHash[node.getId() || node.internalId] = node;
74931     },
74932
74933     /**
74934      * Unregisters a node with the tree
74935      * @private
74936      * @param {Ext.data.NodeInterface} The node to unregister
74937      */
74938     unregisterNode : function(node) {
74939         delete this.nodeHash[node.getId() || node.internalId];
74940     },
74941     
74942     /**
74943      * Sorts this tree
74944      * @private
74945      * @param {Function} sorterFn The function to use for sorting
74946      * @param {Boolean} recursive True to perform recursive sorting
74947      */
74948     sort: function(sorterFn, recursive) {
74949         this.getRootNode().sort(sorterFn, recursive);
74950     },
74951     
74952      /**
74953      * Filters this tree
74954      * @private
74955      * @param {Function} sorterFn The function to use for filtering
74956      * @param {Boolean} recursive True to perform recursive filtering
74957      */
74958     filter: function(filters, recursive) {
74959         this.getRootNode().filter(filters, recursive);
74960     }
74961 });
74962 /**
74963  * @class Ext.data.TreeStore
74964  * @extends Ext.data.AbstractStore
74965  * 
74966  * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}.
74967  * It provides convenience methods for loading nodes, as well as the ability to use
74968  * the hierarchical tree structure combined with a store. This class is generally used
74969  * in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
74970  * the Tree for convenience.
74971  * 
74972  * ## Using Models
74973  * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
74974  * The standard Tree fields will also be copied onto the Model for maintaining their state.
74975  * 
74976  * ## Reading Nested Data
74977  * For the tree to read nested data, the {@link Ext.data.Reader} must be configured with a root property,
74978  * so the reader can find nested data for each node. If a root is not specified, it will default to
74979  * 'children'.
74980  */
74981 Ext.define('Ext.data.TreeStore', {
74982     extend: 'Ext.data.AbstractStore',
74983     alias: 'store.tree',
74984     requires: ['Ext.data.Tree', 'Ext.data.NodeInterface', 'Ext.data.NodeStore'],
74985
74986     /**
74987      * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing
74988      * child nodes before loading.
74989      */
74990     clearOnLoad : true,
74991
74992     /**
74993      * @cfg {String} nodeParam The name of the parameter sent to the server which contains
74994      * the identifier of the node. Defaults to <tt>'node'</tt>.
74995      */
74996     nodeParam: 'node',
74997
74998     /**
74999      * @cfg {String} defaultRootId
75000      * The default root id. Defaults to 'root'
75001      */
75002     defaultRootId: 'root',
75003     
75004     /**
75005      * @cfg {String} defaultRootProperty
75006      * The root property to specify on the reader if one is not explicitly defined.
75007      */
75008     defaultRootProperty: 'children',
75009
75010     /**
75011      * @cfg {Boolean} folderSort Set to true to automatically prepend a leaf sorter (defaults to <tt>undefined</tt>)
75012      */
75013     folderSort: false,
75014     
75015     constructor: function(config) {
75016         var me = this, 
75017             root,
75018             fields;
75019             
75020         
75021         config = Ext.apply({}, config);
75022         
75023         /**
75024          * If we have no fields declare for the store, add some defaults.
75025          * These will be ignored if a model is explicitly specified.
75026          */
75027         fields = config.fields || me.fields;
75028         if (!fields) {
75029             config.fields = [{name: 'text', type: 'string'}];
75030         }
75031
75032         me.callParent([config]);
75033         
75034         // We create our data tree.
75035         me.tree = Ext.create('Ext.data.Tree');
75036         
75037         me.tree.on({
75038             scope: me,
75039             remove: me.onNodeRemove,
75040             beforeexpand: me.onBeforeNodeExpand,
75041             beforecollapse: me.onBeforeNodeCollapse,
75042             append: me.onNodeAdded,
75043             insert: me.onNodeAdded
75044         });
75045
75046         me.onBeforeSort();
75047                 
75048         root = me.root;
75049         if (root) {
75050             delete me.root;
75051             me.setRootNode(root);            
75052         }
75053
75054         me.relayEvents(me.tree, [
75055             /**
75056              * @event append
75057              * Fires when a new child node is appended to a node in this store's tree.
75058              * @param {Tree} tree The owner tree
75059              * @param {Node} parent The parent node
75060              * @param {Node} node The newly appended node
75061              * @param {Number} index The index of the newly appended node
75062              */
75063             "append",
75064             
75065             /**
75066              * @event remove
75067              * Fires when a child node is removed from a node in this store's tree.
75068              * @param {Tree} tree The owner tree
75069              * @param {Node} parent The parent node
75070              * @param {Node} node The child node removed
75071              */
75072             "remove",
75073             
75074             /**
75075              * @event move
75076              * Fires when a node is moved to a new location in the store's tree
75077              * @param {Tree} tree The owner tree
75078              * @param {Node} node The node moved
75079              * @param {Node} oldParent The old parent of this node
75080              * @param {Node} newParent The new parent of this node
75081              * @param {Number} index The index it was moved to
75082              */
75083             "move",
75084             
75085             /**
75086              * @event insert
75087              * Fires when a new child node is inserted in a node in this store's tree.
75088              * @param {Tree} tree The owner tree
75089              * @param {Node} parent The parent node
75090              * @param {Node} node The child node inserted
75091              * @param {Node} refNode The child node the node was inserted before
75092              */
75093             "insert",
75094             
75095             /**
75096              * @event beforeappend
75097              * Fires before a new child is appended to a node in this store's tree, return false to cancel the append.
75098              * @param {Tree} tree The owner tree
75099              * @param {Node} parent The parent node
75100              * @param {Node} node The child node to be appended
75101              */
75102             "beforeappend",
75103             
75104             /**
75105              * @event beforeremove
75106              * Fires before a child is removed from a node in this store's tree, return false to cancel the remove.
75107              * @param {Tree} tree The owner tree
75108              * @param {Node} parent The parent node
75109              * @param {Node} node The child node to be removed
75110              */
75111             "beforeremove",
75112             
75113             /**
75114              * @event beforemove
75115              * Fires before a node is moved to a new location in the store's tree. Return false to cancel the move.
75116              * @param {Tree} tree The owner tree
75117              * @param {Node} node The node being moved
75118              * @param {Node} oldParent The parent of the node
75119              * @param {Node} newParent The new parent the node is moving to
75120              * @param {Number} index The index it is being moved to
75121              */
75122             "beforemove",
75123             
75124             /**
75125              * @event beforeinsert
75126              * Fires before a new child is inserted in a node in this store's tree, return false to cancel the insert.
75127              * @param {Tree} tree The owner tree
75128              * @param {Node} parent The parent node
75129              * @param {Node} node The child node to be inserted
75130              * @param {Node} refNode The child node the node is being inserted before
75131              */
75132             "beforeinsert",
75133              
75134              /**
75135               * @event expand
75136               * Fires when this node is expanded.
75137               * @param {Node} this The expanding node
75138               */
75139              "expand",
75140              
75141              /**
75142               * @event collapse
75143               * Fires when this node is collapsed.
75144               * @param {Node} this The collapsing node
75145               */
75146              "collapse",
75147              
75148              /**
75149               * @event beforeexpand
75150               * Fires before this node is expanded.
75151               * @param {Node} this The expanding node
75152               */
75153              "beforeexpand",
75154              
75155              /**
75156               * @event beforecollapse
75157               * Fires before this node is collapsed.
75158               * @param {Node} this The collapsing node
75159               */
75160              "beforecollapse",
75161
75162              /**
75163               * @event sort
75164               * Fires when this TreeStore is sorted.
75165               * @param {Node} node The node that is sorted.
75166               */             
75167              "sort",
75168              
75169              /**
75170               * @event rootchange
75171               * Fires whenever the root node is changed in the tree.
75172               * @param {Ext.data.Model} root The new root
75173               */
75174              "rootchange"
75175         ]);
75176         
75177         me.addEvents(
75178             /**
75179              * @event rootchange
75180              * Fires when the root node on this TreeStore is changed.
75181              * @param {Ext.data.TreeStore} store This TreeStore
75182              * @param {Node} The new root node.
75183              */
75184             'rootchange'
75185         );
75186         
75187         if (Ext.isDefined(me.nodeParameter)) {
75188             if (Ext.isDefined(Ext.global.console)) {
75189                 Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');
75190             }
75191             me.nodeParam = me.nodeParameter;
75192             delete me.nodeParameter;
75193         }
75194     },
75195     
75196     // inherit docs
75197     setProxy: function(proxy) {
75198         var reader,
75199             needsRoot;
75200         
75201         if (proxy instanceof Ext.data.proxy.Proxy) {
75202             // proxy instance, check if a root was set
75203             needsRoot = Ext.isEmpty(proxy.getReader().root);
75204         } else if (Ext.isString(proxy)) {
75205             // string type, means a reader can't be set
75206             needsRoot = true;
75207         } else {
75208             // object, check if a reader and a root were specified.
75209             reader = proxy.reader;
75210             needsRoot = !(reader && !Ext.isEmpty(reader.root));
75211         }
75212         proxy = this.callParent(arguments);
75213         if (needsRoot) {
75214             reader = proxy.getReader();
75215             reader.root = this.defaultRootProperty;
75216             // force rebuild
75217             reader.buildExtractors(true);
75218         }
75219     },
75220     
75221     // inherit docs
75222     onBeforeSort: function() {
75223         if (this.folderSort) {
75224             this.sort({
75225                 property: 'leaf',
75226                 direction: 'ASC'
75227             }, 'prepend', false);    
75228         }
75229     },
75230     
75231     /**
75232      * Called before a node is expanded.
75233      * @private
75234      * @param {Ext.data.NodeInterface} node The node being expanded.
75235      * @param {Function} callback The function to run after the expand finishes
75236      * @param {Object} scope The scope in which to run the callback function
75237      */
75238     onBeforeNodeExpand: function(node, callback, scope) {
75239         if (node.isLoaded()) {
75240             Ext.callback(callback, scope || node, [node.childNodes]);
75241         }
75242         else if (node.isLoading()) {
75243             this.on('load', function() {
75244                 Ext.callback(callback, scope || node, [node.childNodes]);
75245             }, this, {single: true});
75246         }
75247         else {
75248             this.read({
75249                 node: node,
75250                 callback: function() {
75251                     Ext.callback(callback, scope || node, [node.childNodes]);
75252                 }
75253             });            
75254         }
75255     },
75256     
75257     //inherit docs
75258     getNewRecords: function() {
75259         return Ext.Array.filter(this.tree.flatten(), this.filterNew);
75260     },
75261
75262     //inherit docs
75263     getUpdatedRecords: function() {
75264         return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
75265     },
75266     
75267     /**
75268      * Called before a node is collapsed.
75269      * @private
75270      * @param {Ext.data.NodeInterface} node The node being collapsed.
75271      * @param {Function} callback The function to run after the collapse finishes
75272      * @param {Object} scope The scope in which to run the callback function
75273      */
75274     onBeforeNodeCollapse: function(node, callback, scope) {
75275         callback.call(scope || node, node.childNodes);
75276     },
75277     
75278     onNodeRemove: function(parent, node) {
75279         var removed = this.removed;
75280         
75281         if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {
75282             removed.push(node);
75283         }
75284     },
75285     
75286     onNodeAdded: function(parent, node) {
75287         var proxy = this.getProxy(),
75288             reader = proxy.getReader(),
75289             data = node.raw || node.data,
75290             dataRoot, children;
75291             
75292         Ext.Array.remove(this.removed, node); 
75293         
75294         if (!node.isLeaf() && !node.isLoaded()) {
75295             dataRoot = reader.getRoot(data);
75296             if (dataRoot) {
75297                 this.fillNode(node, reader.extractData(dataRoot));
75298                 delete data[reader.root];
75299             }
75300         }
75301     },
75302         
75303     /**
75304      * Sets the root node for this store
75305      * @param {Ext.data.Model/Ext.data.NodeInterface} root
75306      * @return {Ext.data.NodeInterface} The new root
75307      */
75308     setRootNode: function(root) {
75309         var me = this;
75310
75311         root = root || {};        
75312         if (!root.isNode) {
75313             // create a default rootNode and create internal data struct.        
75314             Ext.applyIf(root, {
75315                 id: me.defaultRootId,
75316                 text: 'Root',
75317                 allowDrag: false
75318             });
75319             root = Ext.ModelManager.create(root, me.model);
75320         }
75321         Ext.data.NodeInterface.decorate(root);
75322
75323         // Because we have decorated the model with new fields,
75324         // we need to build new extactor functions on the reader.
75325         me.getProxy().getReader().buildExtractors(true);
75326         
75327         // When we add the root to the tree, it will automaticaly get the NodeInterface
75328         me.tree.setRootNode(root);
75329         
75330         // If the user has set expanded: true on the root, we want to call the expand function
75331         if (!root.isLoaded() && root.isExpanded()) {
75332             me.load({
75333                 node: root
75334             });
75335         }
75336         
75337         return root;
75338     },
75339         
75340     /**
75341      * Returns the root node for this tree.
75342      * @return {Ext.data.NodeInterface}
75343      */
75344     getRootNode: function() {
75345         return this.tree.getRootNode();
75346     },
75347
75348     /**
75349      * Returns the record node by id
75350      * @return {Ext.data.NodeInterface}
75351      */
75352     getNodeById: function(id) {
75353         return this.tree.getNodeById(id);
75354     },
75355
75356     /**
75357      * Loads the Store using its configured {@link #proxy}.
75358      * @param {Object} options Optional config object. This is passed into the {@link Ext.data.Operation Operation}
75359      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
75360      * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
75361      * default to the root node.
75362      */
75363     load: function(options) {
75364         options = options || {};
75365         options.params = options.params || {};
75366         
75367         var me = this,
75368             node = options.node || me.tree.getRootNode(),
75369             root;
75370             
75371         // If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
75372         // create one for them.
75373         if (!node) {
75374             node = me.setRootNode({
75375                 expanded: true
75376             });
75377         }
75378         
75379         if (me.clearOnLoad) {
75380             node.removeAll();
75381         }
75382         
75383         Ext.applyIf(options, {
75384             node: node
75385         });
75386         options.params[me.nodeParam] = node ? node.getId() : 'root';
75387         
75388         if (node) {
75389             node.set('loading', true);
75390         }
75391         
75392         return me.callParent([options]);
75393     },
75394         
75395
75396     /**
75397      * Fills a node with a series of child records.
75398      * @private
75399      * @param {Ext.data.NodeInterface} node The node to fill
75400      * @param {Array} records The records to add
75401      */
75402     fillNode: function(node, records) {
75403         var me = this,
75404             ln = records ? records.length : 0,
75405             i = 0, sortCollection;
75406
75407         if (ln && me.sortOnLoad && !me.remoteSort && me.sorters && me.sorters.items) {
75408             sortCollection = Ext.create('Ext.util.MixedCollection');
75409             sortCollection.addAll(records);
75410             sortCollection.sort(me.sorters.items);
75411             records = sortCollection.items;
75412         }
75413         
75414         node.set('loaded', true);
75415         for (; i < ln; i++) {
75416             node.appendChild(records[i], undefined, true);
75417         }
75418         
75419         return records;
75420     },
75421
75422     // inherit docs
75423     onProxyLoad: function(operation) {
75424         var me = this,
75425             successful = operation.wasSuccessful(),
75426             records = operation.getRecords(),
75427             node = operation.node;
75428
75429         node.set('loading', false);
75430         if (successful) {
75431             records = me.fillNode(node, records);
75432         }
75433         // deprecate read?
75434         me.fireEvent('read', me, operation.node, records, successful);
75435         me.fireEvent('load', me, operation.node, records, successful);
75436         //this is a callback that would have been passed to the 'read' function and is optional
75437         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
75438     },
75439     
75440     /**
75441      * Create any new records when a write is returned from the server.
75442      * @private
75443      * @param {Array} records The array of new records
75444      * @param {Ext.data.Operation} operation The operation that just completed
75445      * @param {Boolean} success True if the operation was successful
75446      */
75447     onCreateRecords: function(records, operation, success) {
75448         if (success) {
75449             var i = 0,
75450                 length = records.length,
75451                 originalRecords = operation.records,
75452                 parentNode,
75453                 record,
75454                 original,
75455                 index;
75456
75457             /*
75458              * Loop over each record returned from the server. Assume they are
75459              * returned in order of how they were sent. If we find a matching
75460              * record, replace it with the newly created one.
75461              */
75462             for (; i < length; ++i) {
75463                 record = records[i];
75464                 original = originalRecords[i];
75465                 if (original) {
75466                     parentNode = original.parentNode;
75467                     if (parentNode) {
75468                         // prevent being added to the removed cache
75469                         original.isReplace = true;
75470                         parentNode.replaceChild(record, original);
75471                         delete original.isReplace;
75472                     }
75473                     record.phantom = false;
75474                 }
75475             }
75476         }
75477     },
75478
75479     /**
75480      * Update any records when a write is returned from the server.
75481      * @private
75482      * @param {Array} records The array of updated records
75483      * @param {Ext.data.Operation} operation The operation that just completed
75484      * @param {Boolean} success True if the operation was successful
75485      */
75486     onUpdateRecords: function(records, operation, success){
75487         if (success) {
75488             var me = this,
75489                 i = 0,
75490                 length = records.length,
75491                 data = me.data,
75492                 original,
75493                 parentNode,
75494                 record;
75495
75496             for (; i < length; ++i) {
75497                 record = records[i];
75498                 original = me.tree.getNodeById(record.getId());
75499                 parentNode = original.parentNode;
75500                 if (parentNode) {
75501                     // prevent being added to the removed cache
75502                     original.isReplace = true;
75503                     parentNode.replaceChild(record, original);
75504                     original.isReplace = false;
75505                 }
75506             }
75507         }
75508     },
75509
75510     /**
75511      * Remove any records when a write is returned from the server.
75512      * @private
75513      * @param {Array} records The array of removed records
75514      * @param {Ext.data.Operation} operation The operation that just completed
75515      * @param {Boolean} success True if the operation was successful
75516      */
75517     onDestroyRecords: function(records, operation, success){
75518         if (success) {
75519             this.removed = [];
75520         }
75521     },
75522
75523     // inherit docs
75524     removeAll: function() {
75525         this.getRootNode().destroy(true);
75526         this.fireEvent('clear', this);
75527     },
75528
75529     // inherit docs
75530     doSort: function(sorterFn) {
75531         var me = this;
75532         if (me.remoteSort) {
75533             //the load function will pick up the new sorters and request the sorted data from the proxy
75534             me.load();
75535         } else {
75536             me.tree.sort(sorterFn, true);
75537             me.fireEvent('datachanged', me);
75538         }   
75539         me.fireEvent('sort', me);
75540     }
75541 });
75542 /**
75543  * @author Ed Spencer
75544  * @class Ext.data.XmlStore
75545  * @extends Ext.data.Store
75546  * @private
75547  * @ignore
75548  * <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.
75549  * A XmlStore will be automatically configured with a {@link Ext.data.reader.Xml}.</p>
75550  * <p>A store configuration would be something like:<pre><code>
75551 var store = new Ext.data.XmlStore({
75552     // store configs
75553     autoDestroy: true,
75554     storeId: 'myStore',
75555     url: 'sheldon.xml', // automatically configures a HttpProxy
75556     // reader configs
75557     record: 'Item', // records will have an "Item" tag
75558     idPath: 'ASIN',
75559     totalRecords: '@TotalResults'
75560     fields: [
75561         // set up the fields mapping into the xml doc
75562         // The first needs mapping, the others are very basic
75563         {name: 'Author', mapping: 'ItemAttributes > Author'},
75564         'Title', 'Manufacturer', 'ProductGroup'
75565     ]
75566 });
75567  * </code></pre></p>
75568  * <p>This store is configured to consume a returned object of the form:<pre><code>
75569 &#60?xml version="1.0" encoding="UTF-8"?>
75570 &#60ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">
75571     &#60Items>
75572         &#60Request>
75573             &#60IsValid>True&#60/IsValid>
75574             &#60ItemSearchRequest>
75575                 &#60Author>Sidney Sheldon&#60/Author>
75576                 &#60SearchIndex>Books&#60/SearchIndex>
75577             &#60/ItemSearchRequest>
75578         &#60/Request>
75579         &#60TotalResults>203&#60/TotalResults>
75580         &#60TotalPages>21&#60/TotalPages>
75581         &#60Item>
75582             &#60ASIN>0446355453&#60/ASIN>
75583             &#60DetailPageURL>
75584                 http://www.amazon.com/
75585             &#60/DetailPageURL>
75586             &#60ItemAttributes>
75587                 &#60Author>Sidney Sheldon&#60/Author>
75588                 &#60Manufacturer>Warner Books&#60/Manufacturer>
75589                 &#60ProductGroup>Book&#60/ProductGroup>
75590                 &#60Title>Master of the Game&#60/Title>
75591             &#60/ItemAttributes>
75592         &#60/Item>
75593     &#60/Items>
75594 &#60/ItemSearchResponse>
75595  * </code></pre>
75596  * An object literal of this form could also be used as the {@link #data} config option.</p>
75597  * <p><b>Note:</b> Although not listed here, this class accepts all of the configuration options of
75598  * <b>{@link Ext.data.reader.Xml XmlReader}</b>.</p>
75599  * @constructor
75600  * @param {Object} config
75601  * @xtype xmlstore
75602  */
75603 Ext.define('Ext.data.XmlStore', {
75604     extend: 'Ext.data.Store',
75605     alternateClassName: 'Ext.data.XmlStore',
75606     alias: 'store.xml',
75607
75608     /**
75609      * @cfg {Ext.data.DataReader} reader @hide
75610      */
75611     constructor: function(config){
75612         config = config || {};
75613         config = config || {};
75614
75615         Ext.applyIf(config, {
75616             proxy: {
75617                 type: 'ajax',
75618                 reader: 'xml',
75619                 writer: 'xml'
75620             }
75621         });
75622
75623         this.callParent([config]);
75624     }
75625 });
75626
75627 /**
75628  * @author Ed Spencer
75629  * @class Ext.data.proxy.Client
75630  * @extends Ext.data.proxy.Proxy
75631  * 
75632  * <p>Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and 
75633  * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.</p>
75634  */
75635 Ext.define('Ext.data.proxy.Client', {
75636     extend: 'Ext.data.proxy.Proxy',
75637     alternateClassName: 'Ext.data.ClientProxy',
75638     
75639     /**
75640      * Abstract function that must be implemented by each ClientProxy subclass. This should purge all record data
75641      * from the client side storage, as well as removing any supporting data (such as lists of record IDs)
75642      */
75643     clear: function() {
75644         Ext.Error.raise("The Ext.data.proxy.Client subclass that you are using has not defined a 'clear' function. See src/data/ClientProxy.js for details.");
75645     }
75646 });
75647 /**
75648  * @author Ed Spencer
75649  * @class Ext.data.proxy.JsonP
75650  * @extends Ext.data.proxy.Server
75651  *
75652  * <p>JsonPProxy is useful when you need to load data from a domain other than the one your application is running
75653  * on. If your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its
75654  * data from http://domainB.com because cross-domain ajax requests are prohibited by the browser.</p>
75655  *
75656  * <p>We can get around this using a JsonPProxy. JsonPProxy injects a &lt;script&gt; tag into the DOM whenever
75657  * an AJAX request would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag
75658  * that would be injected might look like this:</p>
75659  *
75660 <pre><code>
75661 &lt;script src="http://domainB.com/users?callback=someCallback"&gt;&lt;/script&gt;
75662 </code></pre>
75663  *
75664  * <p>When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
75665  * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we
75666  * want to be notified when the result comes in and that it should call our callback function with the data it sends
75667  * back. So long as the server formats the response to look like this, everything will work:</p>
75668  *
75669 <pre><code>
75670 someCallback({
75671     users: [
75672         {
75673             id: 1,
75674             name: "Ed Spencer",
75675             email: "ed@sencha.com"
75676         }
75677     ]
75678 });
75679 </code></pre>
75680  *
75681  * <p>As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the
75682  * JSON object that the server returned.</p>
75683  *
75684  * <p>JsonPProxy takes care of all of this automatically. It formats the url you pass, adding the callback
75685  * parameter automatically. It even creates a temporary callback function, waits for it to be called and then puts
75686  * the data into the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}.
75687  * Here's how we might set that up:</p>
75688  *
75689 <pre><code>
75690 Ext.define('User', {
75691     extend: 'Ext.data.Model',
75692     fields: ['id', 'name', 'email']
75693 });
75694
75695 var store = new Ext.data.Store({
75696     model: 'User',
75697     proxy: {
75698         type: 'jsonp',
75699         url : 'http://domainB.com/users'
75700     }
75701 });
75702
75703 store.load();
75704 </code></pre>
75705  *
75706  * <p>That's all we need to do - JsonPProxy takes care of the rest. In this case the Proxy will have injected a
75707  * script tag like this:
75708  *
75709 <pre><code>
75710 &lt;script src="http://domainB.com/users?callback=stcCallback001" id="stcScript001"&gt;&lt;/script&gt;
75711 </code></pre>
75712  *
75713  * <p><u>Customization</u></p>
75714  *
75715  * <p>Most parts of this script tag can be customized using the {@link #callbackParam}, {@link #callbackPrefix} and
75716  * {@link #scriptIdPrefix} configurations. For example:
75717  *
75718 <pre><code>
75719 var store = new Ext.data.Store({
75720     model: 'User',
75721     proxy: {
75722         type: 'jsonp',
75723         url : 'http://domainB.com/users',
75724         callbackParam: 'theCallbackFunction',
75725         callbackPrefix: 'ABC',
75726         scriptIdPrefix: 'injectedScript'
75727     }
75728 });
75729
75730 store.load();
75731 </code></pre>
75732  *
75733  * <p>Would inject a script tag like this:</p>
75734  *
75735 <pre><code>
75736 &lt;script src="http://domainB.com/users?theCallbackFunction=ABC001" id="injectedScript001"&gt;&lt;/script&gt;
75737 </code></pre>
75738  *
75739  * <p><u>Implementing on the server side</u></p>
75740  *
75741  * <p>The remote server side needs to be configured to return data in this format. Here are suggestions for how you
75742  * might achieve this using Java, PHP and ASP.net:</p>
75743  *
75744  * <p>Java:</p>
75745  *
75746 <pre><code>
75747 boolean jsonP = false;
75748 String cb = request.getParameter("callback");
75749 if (cb != null) {
75750     jsonP = true;
75751     response.setContentType("text/javascript");
75752 } else {
75753     response.setContentType("application/x-json");
75754 }
75755 Writer out = response.getWriter();
75756 if (jsonP) {
75757     out.write(cb + "(");
75758 }
75759 out.print(dataBlock.toJsonString());
75760 if (jsonP) {
75761     out.write(");");
75762 }
75763 </code></pre>
75764  *
75765  * <p>PHP:</p>
75766  *
75767 <pre><code>
75768 $callback = $_REQUEST['callback'];
75769
75770 // Create the output object.
75771 $output = array('a' => 'Apple', 'b' => 'Banana');
75772
75773 //start output
75774 if ($callback) {
75775     header('Content-Type: text/javascript');
75776     echo $callback . '(' . json_encode($output) . ');';
75777 } else {
75778     header('Content-Type: application/x-json');
75779     echo json_encode($output);
75780 }
75781 </code></pre>
75782  *
75783  * <p>ASP.net:</p>
75784  *
75785 <pre><code>
75786 String jsonString = "{success: true}";
75787 String cb = Request.Params.Get("callback");
75788 String responseString = "";
75789 if (!String.IsNullOrEmpty(cb)) {
75790     responseString = cb + "(" + jsonString + ")";
75791 } else {
75792     responseString = jsonString;
75793 }
75794 Response.Write(responseString);
75795 </code></pre>
75796  *
75797  */
75798 Ext.define('Ext.data.proxy.JsonP', {
75799     extend: 'Ext.data.proxy.Server',
75800     alternateClassName: 'Ext.data.ScriptTagProxy',
75801     alias: ['proxy.jsonp', 'proxy.scripttag'],
75802     requires: ['Ext.data.JsonP'],
75803
75804     defaultWriterType: 'base',
75805
75806     /**
75807      * @cfg {String} callbackKey (Optional) See {@link Ext.data.JsonP#callbackKey}.
75808      */
75809     callbackKey : 'callback',
75810
75811     /**
75812      * @cfg {String} recordParam
75813      * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString').
75814      * Defaults to 'records'
75815      */
75816     recordParam: 'records',
75817
75818     /**
75819      * @cfg {Boolean} autoAppendParams True to automatically append the request's params to the generated url. Defaults to true
75820      */
75821     autoAppendParams: true,
75822
75823     constructor: function(){
75824         this.addEvents(
75825             /**
75826              * @event exception
75827              * Fires when the server returns an exception
75828              * @param {Ext.data.proxy.Proxy} this
75829              * @param {Ext.data.Request} request The request that was sent
75830              * @param {Ext.data.Operation} operation The operation that triggered the request
75831              */
75832             'exception'
75833         );
75834         this.callParent(arguments);
75835     },
75836
75837     /**
75838      * @private
75839      * Performs the read request to the remote domain. JsonPProxy does not actually create an Ajax request,
75840      * instead we write out a <script> tag based on the configuration of the internal Ext.data.Request object
75841      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
75842      * @param {Function} callback A callback function to execute when the Operation has been completed
75843      * @param {Object} scope The scope to execute the callback in
75844      */
75845     doRequest: function(operation, callback, scope) {
75846         //generate the unique IDs for this request
75847         var me      = this,
75848             writer  = me.getWriter(),
75849             request = me.buildRequest(operation),
75850             params = request.params;
75851
75852         if (operation.allowWrite()) {
75853             request = writer.write(request);
75854         }
75855
75856         //apply JsonPProxy-specific attributes to the Request
75857         Ext.apply(request, {
75858             callbackKey: me.callbackKey,
75859             timeout: me.timeout,
75860             scope: me,
75861             disableCaching: false, // handled by the proxy
75862             callback: me.createRequestCallback(request, operation, callback, scope)
75863         });
75864         
75865         // prevent doubling up
75866         if (me.autoAppendParams) {
75867             request.params = {};
75868         }
75869         
75870         request.jsonp = Ext.data.JsonP.request(request);
75871         // restore on the request
75872         request.params = params;
75873         operation.setStarted();
75874         me.lastRequest = request;
75875
75876         return request;
75877     },
75878
75879     /**
75880      * @private
75881      * Creates and returns the function that is called when the request has completed. The returned function
75882      * should accept a Response object, which contains the response to be read by the configured Reader.
75883      * The third argument is the callback that should be called after the request has been completed and the Reader has decoded
75884      * the response. This callback will typically be the callback passed by a store, e.g. in proxy.read(operation, theCallback, scope)
75885      * theCallback refers to the callback argument received by this function.
75886      * See {@link #doRequest} for details.
75887      * @param {Ext.data.Request} request The Request object
75888      * @param {Ext.data.Operation} operation The Operation being executed
75889      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
75890      * passed to doRequest
75891      * @param {Object} scope The scope in which to execute the callback function
75892      * @return {Function} The callback function
75893      */
75894     createRequestCallback: function(request, operation, callback, scope) {
75895         var me = this;
75896
75897         return function(success, response, errorType) {
75898             delete me.lastRequest;
75899             me.processResponse(success, operation, request, response, callback, scope);
75900         };
75901     },
75902     
75903     // inherit docs
75904     setException: function(operation, response) {
75905         operation.setException(operation.request.jsonp.errorType);
75906     },
75907
75908
75909     /**
75910      * Generates a url based on a given Ext.data.Request object. Adds the params and callback function name to the url
75911      * @param {Ext.data.Request} request The request object
75912      * @return {String} The url
75913      */
75914     buildUrl: function(request) {
75915         var me      = this,
75916             url     = me.callParent(arguments),
75917             params  = Ext.apply({}, request.params),
75918             filters = params.filters,
75919             records,
75920             filter, i;
75921
75922         delete params.filters;
75923  
75924         if (me.autoAppendParams) {
75925             url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
75926         }
75927
75928         if (filters && filters.length) {
75929             for (i = 0; i < filters.length; i++) {
75930                 filter = filters[i];
75931
75932                 if (filter.value) {
75933                     url = Ext.urlAppend(url, filter.property + "=" + filter.value);
75934                 }
75935             }
75936         }
75937
75938         //if there are any records present, append them to the url also
75939         records = request.records;
75940
75941         if (Ext.isArray(records) && records.length > 0) {
75942             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.recordParam, me.encodeRecords(records)));
75943         }
75944
75945         return url;
75946     },
75947
75948     //inherit docs
75949     destroy: function() {
75950         this.abort();
75951         this.callParent();
75952     },
75953
75954     /**
75955      * Aborts the current server request if one is currently running
75956      */
75957     abort: function() {
75958         var lastRequest = this.lastRequest;
75959         if (lastRequest) {
75960             Ext.data.JsonP.abort(lastRequest.jsonp);
75961         }
75962     },
75963
75964     /**
75965      * Encodes an array of records into a string suitable to be appended to the script src url. This is broken
75966      * out into its own function so that it can be easily overridden.
75967      * @param {Array} records The records array
75968      * @return {String} The encoded records string
75969      */
75970     encodeRecords: function(records) {
75971         var encoded = "",
75972             i = 0,
75973             len = records.length;
75974
75975         for (; i < len; i++) {
75976             encoded += Ext.Object.toQueryString(records[i].data);
75977         }
75978
75979         return encoded;
75980     }
75981 });
75982
75983 /**
75984  * @author Ed Spencer
75985  * @class Ext.data.proxy.WebStorage
75986  * @extends Ext.data.proxy.Client
75987  * 
75988  * <p>WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage localStorage} and 
75989  * {@link Ext.data.proxy.SessionStorage sessionStorage} proxies. It uses the new HTML5 key/value client-side storage 
75990  * objects to save {@link Ext.data.Model model instances} for offline use.</p>
75991  * 
75992  * @constructor
75993  * Creates the proxy, throws an error if local storage is not supported in the current browser
75994  * @param {Object} config Optional config object
75995  */
75996 Ext.define('Ext.data.proxy.WebStorage', {
75997     extend: 'Ext.data.proxy.Client',
75998     alternateClassName: 'Ext.data.WebStorageProxy',
75999     
76000     /**
76001      * @cfg {String} id The unique ID used as the key in which all record data are stored in the local storage object
76002      */
76003     id: undefined,
76004
76005     /**
76006      * @ignore
76007      */
76008     constructor: function(config) {
76009         this.callParent(arguments);
76010         
76011         /**
76012          * Cached map of records already retrieved by this Proxy - ensures that the same instance is always retrieved
76013          * @property cache
76014          * @type Object
76015          */
76016         this.cache = {};
76017
76018         if (this.getStorageObject() === undefined) {
76019             Ext.Error.raise("Local Storage is not supported in this browser, please use another type of data proxy");
76020         }
76021
76022         //if an id is not given, try to use the store's id instead
76023         this.id = this.id || (this.store ? this.store.storeId : undefined);
76024
76025         if (this.id === undefined) {
76026             Ext.Error.raise("No unique id was provided to the local storage proxy. See Ext.data.proxy.LocalStorage documentation for details");
76027         }
76028
76029         this.initialize();
76030     },
76031
76032     //inherit docs
76033     create: function(operation, callback, scope) {
76034         var records = operation.records,
76035             length  = records.length,
76036             ids     = this.getIds(),
76037             id, record, i;
76038         
76039         operation.setStarted();
76040
76041         for (i = 0; i < length; i++) {
76042             record = records[i];
76043
76044             if (record.phantom) {
76045                 record.phantom = false;
76046                 id = this.getNextId();
76047             } else {
76048                 id = record.getId();
76049             }
76050
76051             this.setRecord(record, id);
76052             ids.push(id);
76053         }
76054
76055         this.setIds(ids);
76056
76057         operation.setCompleted();
76058         operation.setSuccessful();
76059
76060         if (typeof callback == 'function') {
76061             callback.call(scope || this, operation);
76062         }
76063     },
76064
76065     //inherit docs
76066     read: function(operation, callback, scope) {
76067         //TODO: respect sorters, filters, start and limit options on the Operation
76068
76069         var records = [],
76070             ids     = this.getIds(),
76071             length  = ids.length,
76072             i, recordData, record;
76073         
76074         //read a single record
76075         if (operation.id) {
76076             record = this.getRecord(operation.id);
76077             
76078             if (record) {
76079                 records.push(record);
76080                 operation.setSuccessful();
76081             }
76082         } else {
76083             for (i = 0; i < length; i++) {
76084                 records.push(this.getRecord(ids[i]));
76085             }
76086             operation.setSuccessful();
76087         }
76088         
76089         operation.setCompleted();
76090
76091         operation.resultSet = Ext.create('Ext.data.ResultSet', {
76092             records: records,
76093             total  : records.length,
76094             loaded : true
76095         });
76096
76097         if (typeof callback == 'function') {
76098             callback.call(scope || this, operation);
76099         }
76100     },
76101
76102     //inherit docs
76103     update: function(operation, callback, scope) {
76104         var records = operation.records,
76105             length  = records.length,
76106             ids     = this.getIds(),
76107             record, id, i;
76108
76109         operation.setStarted();
76110
76111         for (i = 0; i < length; i++) {
76112             record = records[i];
76113             this.setRecord(record);
76114             
76115             //we need to update the set of ids here because it's possible that a non-phantom record was added
76116             //to this proxy - in which case the record's id would never have been added via the normal 'create' call
76117             id = record.getId();
76118             if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
76119                 ids.push(id);
76120             }
76121         }
76122         this.setIds(ids);
76123
76124         operation.setCompleted();
76125         operation.setSuccessful();
76126
76127         if (typeof callback == 'function') {
76128             callback.call(scope || this, operation);
76129         }
76130     },
76131
76132     //inherit
76133     destroy: function(operation, callback, scope) {
76134         var records = operation.records,
76135             length  = records.length,
76136             ids     = this.getIds(),
76137
76138             //newIds is a copy of ids, from which we remove the destroyed records
76139             newIds  = [].concat(ids),
76140             i;
76141
76142         for (i = 0; i < length; i++) {
76143             Ext.Array.remove(newIds, records[i].getId());
76144             this.removeRecord(records[i], false);
76145         }
76146
76147         this.setIds(newIds);
76148         
76149         operation.setCompleted();
76150         operation.setSuccessful();
76151
76152         if (typeof callback == 'function') {
76153             callback.call(scope || this, operation);
76154         }
76155     },
76156
76157     /**
76158      * @private
76159      * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data
76160      * @param {String} id The record's unique ID
76161      * @return {Ext.data.Model} The model instance
76162      */
76163     getRecord: function(id) {
76164         if (this.cache[id] === undefined) {
76165             var rawData = Ext.decode(this.getStorageObject().getItem(this.getRecordKey(id))),
76166                 data    = {},
76167                 Model   = this.model,
76168                 fields  = Model.prototype.fields.items,
76169                 length  = fields.length,
76170                 i, field, name, record;
76171
76172             for (i = 0; i < length; i++) {
76173                 field = fields[i];
76174                 name  = field.name;
76175
76176                 if (typeof field.decode == 'function') {
76177                     data[name] = field.decode(rawData[name]);
76178                 } else {
76179                     data[name] = rawData[name];
76180                 }
76181             }
76182
76183             record = new Model(data, id);
76184             record.phantom = false;
76185
76186             this.cache[id] = record;
76187         }
76188         
76189         return this.cache[id];
76190     },
76191
76192     /**
76193      * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data
76194      * @param {Ext.data.Model} record The model instance
76195      * @param {String} id The id to save the record under (defaults to the value of the record's getId() function)
76196      */
76197     setRecord: function(record, id) {
76198         if (id) {
76199             record.setId(id);
76200         } else {
76201             id = record.getId();
76202         }
76203
76204         var me = this,
76205             rawData = record.data,
76206             data    = {},
76207             model   = me.model,
76208             fields  = model.prototype.fields.items,
76209             length  = fields.length,
76210             i = 0,
76211             field, name, obj, key;
76212
76213         for (; i < length; i++) {
76214             field = fields[i];
76215             name  = field.name;
76216
76217             if (typeof field.encode == 'function') {
76218                 data[name] = field.encode(rawData[name], record);
76219             } else {
76220                 data[name] = rawData[name];
76221             }
76222         }
76223
76224         obj = me.getStorageObject();
76225         key = me.getRecordKey(id);
76226         
76227         //keep the cache up to date
76228         me.cache[id] = record;
76229         
76230         //iPad bug requires that we remove the item before setting it
76231         obj.removeItem(key);
76232         obj.setItem(key, Ext.encode(data));
76233     },
76234
76235     /**
76236      * @private
76237      * Physically removes a given record from the local storage. Used internally by {@link #destroy}, which you should
76238      * use instead because it updates the list of currently-stored record ids
76239      * @param {String|Number|Ext.data.Model} id The id of the record to remove, or an Ext.data.Model instance
76240      */
76241     removeRecord: function(id, updateIds) {
76242         var me = this,
76243             ids;
76244             
76245         if (id.isModel) {
76246             id = id.getId();
76247         }
76248
76249         if (updateIds !== false) {
76250             ids = me.getIds();
76251             Ext.Array.remove(ids, id);
76252             me.setIds(ids);
76253         }
76254
76255         me.getStorageObject().removeItem(me.getRecordKey(id));
76256     },
76257
76258     /**
76259      * @private
76260      * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
76261      * storing data in the local storage object and should prevent naming collisions.
76262      * @param {String|Number|Ext.data.Model} id The record id, or a Model instance
76263      * @return {String} The unique key for this record
76264      */
76265     getRecordKey: function(id) {
76266         if (id.isModel) {
76267             id = id.getId();
76268         }
76269
76270         return Ext.String.format("{0}-{1}", this.id, id);
76271     },
76272
76273     /**
76274      * @private
76275      * Returns the unique key used to store the current record counter for this proxy. This is used internally when
76276      * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
76277      * @return {String} The counter key
76278      */
76279     getRecordCounterKey: function() {
76280         return Ext.String.format("{0}-counter", this.id);
76281     },
76282
76283     /**
76284      * @private
76285      * Returns the array of record IDs stored in this Proxy
76286      * @return {Array} The record IDs. Each is cast as a Number
76287      */
76288     getIds: function() {
76289         var ids    = (this.getStorageObject().getItem(this.id) || "").split(","),
76290             length = ids.length,
76291             i;
76292
76293         if (length == 1 && ids[0] === "") {
76294             ids = [];
76295         } else {
76296             for (i = 0; i < length; i++) {
76297                 ids[i] = parseInt(ids[i], 10);
76298             }
76299         }
76300
76301         return ids;
76302     },
76303
76304     /**
76305      * @private
76306      * Saves the array of ids representing the set of all records in the Proxy
76307      * @param {Array} ids The ids to set
76308      */
76309     setIds: function(ids) {
76310         var obj = this.getStorageObject(),
76311             str = ids.join(",");
76312         
76313         obj.removeItem(this.id);
76314         
76315         if (!Ext.isEmpty(str)) {
76316             obj.setItem(this.id, str);
76317         }
76318     },
76319
76320     /**
76321      * @private
76322      * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey). Increments
76323      * the counter.
76324      * @return {Number} The id
76325      */
76326     getNextId: function() {
76327         var obj  = this.getStorageObject(),
76328             key  = this.getRecordCounterKey(),
76329             last = obj.getItem(key),
76330             ids, id;
76331         
76332         if (last === null) {
76333             ids = this.getIds();
76334             last = ids[ids.length - 1] || 0;
76335         }
76336         
76337         id = parseInt(last, 10) + 1;
76338         obj.setItem(key, id);
76339         
76340         return id;
76341     },
76342
76343     /**
76344      * @private
76345      * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
76346      * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
76347      */
76348     initialize: function() {
76349         var storageObject = this.getStorageObject();
76350         storageObject.setItem(this.id, storageObject.getItem(this.id) || "");
76351     },
76352
76353     /**
76354      * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the storage object
76355      */
76356     clear: function() {
76357         var obj = this.getStorageObject(),
76358             ids = this.getIds(),
76359             len = ids.length,
76360             i;
76361
76362         //remove all the records
76363         for (i = 0; i < len; i++) {
76364             this.removeRecord(ids[i]);
76365         }
76366
76367         //remove the supporting objects
76368         obj.removeItem(this.getRecordCounterKey());
76369         obj.removeItem(this.id);
76370     },
76371
76372     /**
76373      * @private
76374      * Abstract function which should return the storage object that data will be saved to. This must be implemented
76375      * in each subclass.
76376      * @return {Object} The storage object
76377      */
76378     getStorageObject: function() {
76379         Ext.Error.raise("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");
76380     }
76381 });
76382 /**
76383  * @author Ed Spencer
76384  * @class Ext.data.proxy.LocalStorage
76385  * @extends Ext.data.proxy.WebStorage
76386  * 
76387  * <p>The LocalStorageProxy uses the new HTML5 localStorage API to save {@link Ext.data.Model Model} data locally on
76388  * the client browser. HTML5 localStorage is a key-value store (e.g. cannot save complex objects like JSON), so
76389  * LocalStorageProxy automatically serializes and deserializes data when saving and retrieving it.</p>
76390  * 
76391  * <p>localStorage is extremely useful for saving user-specific information without needing to build server-side 
76392  * infrastructure to support it. Let's imagine we're writing a Twitter search application and want to save the user's
76393  * searches locally so they can easily perform a saved search again later. We'd start by creating a Search model:</p>
76394  * 
76395 <pre><code>
76396 Ext.define('Search', {
76397     fields: ['id', 'query'],
76398     extend: 'Ext.data.Model',
76399     proxy: {
76400         type: 'localstorage',
76401         id  : 'twitter-Searches'
76402     }
76403 });
76404 </code></pre>
76405  * 
76406  * <p>Our Search model contains just two fields - id and query - plus a Proxy definition. The only configuration we
76407  * need to pass to the LocalStorage proxy is an {@link #id}. This is important as it separates the Model data in this
76408  * Proxy from all others. The localStorage API puts all data into a single shared namespace, so by setting an id we
76409  * enable LocalStorageProxy to manage the saved Search data.</p>
76410  * 
76411  * <p>Saving our data into localStorage is easy and would usually be done with a {@link Ext.data.Store Store}:</p>
76412  * 
76413 <pre><code>
76414 //our Store automatically picks up the LocalStorageProxy defined on the Search model
76415 var store = new Ext.data.Store({
76416     model: "Search"
76417 });
76418
76419 //loads any existing Search data from localStorage
76420 store.load();
76421
76422 //now add some Searches
76423 store.add({query: 'Sencha Touch'});
76424 store.add({query: 'Ext JS'});
76425
76426 //finally, save our Search data to localStorage
76427 store.sync();
76428 </code></pre>
76429  * 
76430  * <p>The LocalStorageProxy automatically gives our new Searches an id when we call store.sync(). It encodes the Model
76431  * data and places it into localStorage. We can also save directly to localStorage, bypassing the Store altogether:</p>
76432  * 
76433 <pre><code>
76434 var search = Ext.ModelManager.create({query: 'Sencha Animator'}, 'Search');
76435
76436 //uses the configured LocalStorageProxy to save the new Search to localStorage
76437 search.save();
76438 </code></pre>
76439  * 
76440  * <p><u>Limitations</u></p>
76441  * 
76442  * <p>If this proxy is used in a browser where local storage is not supported, the constructor will throw an error.
76443  * A local storage proxy requires a unique ID which is used as a key in which all record data are stored in the
76444  * local storage object.</p>
76445  * 
76446  * <p>It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided
76447  * but the attached store has a storeId, the storeId will be used. If neither option is presented the proxy will
76448  * throw an error.</p>
76449  */
76450 Ext.define('Ext.data.proxy.LocalStorage', {
76451     extend: 'Ext.data.proxy.WebStorage',
76452     alias: 'proxy.localstorage',
76453     alternateClassName: 'Ext.data.LocalStorageProxy',
76454     
76455     //inherit docs
76456     getStorageObject: function() {
76457         return window.localStorage;
76458     }
76459 });
76460 /**
76461  * @author Ed Spencer
76462  * @class Ext.data.proxy.Memory
76463  * @extends Ext.data.proxy.Client
76464  *
76465  * <p>In-memory proxy. This proxy simply uses a local variable for data storage/retrieval, so its contents are lost on
76466  * every page refresh.</p>
76467  *
76468  * <p>Usually this Proxy isn't used directly, serving instead as a helper to a {@link Ext.data.Store Store} where a
76469  * reader is required to load data. For example, say we have a Store for a User model and have some inline data we want
76470  * to load, but this data isn't in quite the right format: we can use a MemoryProxy with a JsonReader to read it into
76471  * our Store:</p>
76472  *
76473 <pre><code>
76474 //this is the model we will be using in the store
76475 Ext.define('User', {
76476     extend: 'Ext.data.Model',
76477     fields: [
76478         {name: 'id',    type: 'int'},
76479         {name: 'name',  type: 'string'},
76480         {name: 'phone', type: 'string', mapping: 'phoneNumber'}
76481     ]
76482 });
76483
76484 //this data does not line up to our model fields - the phone field is called phoneNumber
76485 var data = {
76486     users: [
76487         {
76488             id: 1,
76489             name: 'Ed Spencer',
76490             phoneNumber: '555 1234'
76491         },
76492         {
76493             id: 2,
76494             name: 'Abe Elias',
76495             phoneNumber: '666 1234'
76496         }
76497     ]
76498 };
76499
76500 //note how we set the 'root' in the reader to match the data structure above
76501 var store = new Ext.data.Store({
76502     autoLoad: true,
76503     model: 'User',
76504     data : data,
76505     proxy: {
76506         type: 'memory',
76507         reader: {
76508             type: 'json',
76509             root: 'users'
76510         }
76511     }
76512 });
76513 </code></pre>
76514  */
76515 Ext.define('Ext.data.proxy.Memory', {
76516     extend: 'Ext.data.proxy.Client',
76517     alias: 'proxy.memory',
76518     alternateClassName: 'Ext.data.MemoryProxy',
76519
76520     /**
76521      * @cfg {Array} data Optional array of Records to load into the Proxy
76522      */
76523
76524     constructor: function(config) {
76525         this.callParent([config]);
76526
76527         //ensures that the reader has been instantiated properly
76528         this.setReader(this.reader);
76529     },
76530
76531     /**
76532      * Reads data from the configured {@link #data} object. Uses the Proxy's {@link #reader}, if present
76533      * @param {Ext.data.Operation} operation The read Operation
76534      * @param {Function} callback The callback to call when reading has completed
76535      * @param {Object} scope The scope to call the callback function in
76536      */
76537     read: function(operation, callback, scope) {
76538         var me     = this,
76539             reader = me.getReader(),
76540             result = reader.read(me.data);
76541
76542         Ext.apply(operation, {
76543             resultSet: result
76544         });
76545
76546         operation.setCompleted();
76547         operation.setSuccessful();
76548         Ext.callback(callback, scope || me, [operation]);
76549     },
76550
76551     clear: Ext.emptyFn
76552 });
76553
76554 /**
76555  * @author Ed Spencer
76556  * @class Ext.data.proxy.Rest
76557  * @extends Ext.data.proxy.Ajax
76558  * 
76559  * <p>RestProxy is a specialization of the {@link Ext.data.proxy.Ajax AjaxProxy} which simply maps the four actions 
76560  * (create, read, update and destroy) to RESTful HTTP verbs. For example, let's set up a {@link Ext.data.Model Model}
76561  * with an inline RestProxy</p>
76562  * 
76563 <pre><code>
76564 Ext.define('User', {
76565     extend: 'Ext.data.Model',
76566     fields: ['id', 'name', 'email'],
76567
76568     proxy: {
76569         type: 'rest',
76570         url : '/users'
76571     }
76572 });
76573 </code></pre>
76574  * 
76575  * <p>Now we can create a new User instance and save it via the RestProxy. Doing this will cause the Proxy to send a
76576  * POST request to '/users':
76577  * 
76578 <pre><code>
76579 var user = Ext.ModelManager.create({name: 'Ed Spencer', email: 'ed@sencha.com'}, 'User');
76580
76581 user.save(); //POST /users
76582 </code></pre>
76583  * 
76584  * <p>Let's expand this a little and provide a callback for the {@link Ext.data.Model#save} call to update the Model
76585  * once it has been created. We'll assume the creation went successfully and that the server gave this user an ID of 
76586  * 123:</p>
76587  * 
76588 <pre><code>
76589 user.save({
76590     success: function(user) {
76591         user.set('name', 'Khan Noonien Singh');
76592
76593         user.save(); //PUT /users/123
76594     }
76595 });
76596 </code></pre>
76597  * 
76598  * <p>Now that we're no longer creating a new Model instance, the request method is changed to an HTTP PUT, targeting
76599  * the relevant url for that user. Now let's delete this user, which will use the DELETE method:</p>
76600  * 
76601 <pre><code>
76602     user.destroy(); //DELETE /users/123
76603 </code></pre>
76604  * 
76605  * <p>Finally, when we perform a load of a Model or Store, RestProxy will use the GET method:</p>
76606  * 
76607 <pre><code>
76608 //1. Load via Store
76609
76610 //the Store automatically picks up the Proxy from the User model
76611 var store = new Ext.data.Store({
76612     model: 'User'
76613 });
76614
76615 store.load(); //GET /users
76616
76617 //2. Load directly from the Model
76618
76619 //GET /users/123
76620 Ext.ModelManager.getModel('User').load(123, {
76621     success: function(user) {
76622         console.log(user.getId()); //outputs 123
76623     }
76624 });
76625 </code></pre>
76626  * 
76627  * <p><u>Url generation</u></p>
76628  * 
76629  * <p>RestProxy is able to automatically generate the urls above based on two configuration options - {@link #appendId}
76630  * and {@link #format}. If appendId is true (it is by default) then RestProxy will automatically append the ID of the 
76631  * Model instance in question to the configured url, resulting in the '/users/123' that we saw above.</p>
76632  * 
76633  * <p>If the request is not for a specific Model instance (e.g. loading a Store), the url is not appended with an id. 
76634  * RestProxy will automatically insert a '/' before the ID if one is not already present.</p>
76635  * 
76636 <pre><code>
76637 new Ext.data.proxy.Rest({
76638     url: '/users',
76639     appendId: true //default
76640 });
76641
76642 // Collection url: /users
76643 // Instance url  : /users/123
76644 </code></pre>
76645  * 
76646  * <p>RestProxy can also optionally append a format string to the end of any generated url:</p>
76647  * 
76648 <pre><code>
76649 new Ext.data.proxy.Rest({
76650     url: '/users',
76651     format: 'json'
76652 });
76653
76654 // Collection url: /users.json
76655 // Instance url  : /users/123.json
76656 </code></pre>
76657  * 
76658  * <p>If further customization is needed, simply implement the {@link #buildUrl} method and add your custom generated
76659  * url onto the {@link Ext.data.Request Request} object that is passed to buildUrl. See 
76660  * <a href="source/RestProxy.html#method-Ext.data.proxy.Rest-buildUrl">RestProxy's implementation</a> for an example of
76661  * how to achieve this.</p>
76662  * 
76663  * <p>Note that RestProxy inherits from {@link Ext.data.proxy.Ajax AjaxProxy}, which already injects all of the sorter,
76664  * filter, group and paging options into the generated url. See the {@link Ext.data.proxy.Ajax AjaxProxy docs} for more
76665  * details.</p>
76666  */
76667 Ext.define('Ext.data.proxy.Rest', {
76668     extend: 'Ext.data.proxy.Ajax',
76669     alternateClassName: 'Ext.data.RestProxy',
76670     alias : 'proxy.rest',
76671     
76672     /**
76673      * @cfg {Boolean} appendId True to automatically append the ID of a Model instance when performing a request based
76674      * on that single instance. See RestProxy intro docs for more details. Defaults to true.
76675      */
76676     appendId: true,
76677     
76678     /**
76679      * @cfg {String} format Optional data format to send to the server when making any request (e.g. 'json'). See the
76680      * RestProxy intro docs for full details. Defaults to undefined.
76681      */
76682     
76683     /**
76684      * @cfg {Boolean} batchActions True to batch actions of a particular type when synchronizing the store.
76685      * Defaults to <tt>false</tt>.
76686      */
76687     batchActions: false,
76688     
76689     /**
76690      * Specialized version of buildUrl that incorporates the {@link #appendId} and {@link #format} options into the
76691      * generated url. Override this to provide further customizations, but remember to call the superclass buildUrl
76692      * so that additional parameters like the cache buster string are appended
76693      */
76694     buildUrl: function(request) {
76695         var me        = this,
76696             operation = request.operation,
76697             records   = operation.records || [],
76698             record    = records[0],
76699             format    = me.format,
76700             url       = me.getUrl(request),
76701             id        = record ? record.getId() : operation.id;
76702         
76703         if (me.appendId && id) {
76704             if (!url.match(/\/$/)) {
76705                 url += '/';
76706             }
76707             
76708             url += id;
76709         }
76710         
76711         if (format) {
76712             if (!url.match(/\.$/)) {
76713                 url += '.';
76714             }
76715             
76716             url += format;
76717         }
76718         
76719         request.url = url;
76720         
76721         return me.callParent(arguments);
76722     }
76723 }, function() {
76724     Ext.apply(this.prototype, {
76725         /**
76726          * Mapping of action name to HTTP request method. These default to RESTful conventions for the 'create', 'read',
76727          * 'update' and 'destroy' actions (which map to 'POST', 'GET', 'PUT' and 'DELETE' respectively). This object should
76728          * not be changed except globally via {@link Ext#override Ext.override} - the {@link #getMethod} function can be overridden instead.
76729          * @property actionMethods
76730          * @type Object
76731          */
76732         actionMethods: {
76733             create : 'POST',
76734             read   : 'GET',
76735             update : 'PUT',
76736             destroy: 'DELETE'
76737         }
76738     });
76739 });
76740
76741 /**
76742  * @author Ed Spencer
76743  * @class Ext.data.proxy.SessionStorage
76744  * @extends Ext.data.proxy.WebStorage
76745  * 
76746  * <p>Proxy which uses HTML5 session storage as its data storage/retrieval mechanism.
76747  * If this proxy is used in a browser where session storage is not supported, the constructor will throw an error.
76748  * A session storage proxy requires a unique ID which is used as a key in which all record data are stored in the
76749  * session storage object.</p>
76750  * 
76751  * <p>It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided
76752  * but the attached store has a storeId, the storeId will be used. If neither option is presented the proxy will
76753  * throw an error.</p>
76754  * 
76755  * <p>Proxies are almost always used with a {@link Ext.data.Store store}:<p>
76756  * 
76757 <pre><code>
76758 new Ext.data.Store({
76759     proxy: {
76760         type: 'sessionstorage',
76761         id  : 'myProxyKey'
76762     }
76763 });
76764 </code></pre>
76765  * 
76766  * <p>Alternatively you can instantiate the Proxy directly:</p>
76767  * 
76768 <pre><code>
76769 new Ext.data.proxy.SessionStorage({
76770     id  : 'myOtherProxyKey'
76771 });
76772  </code></pre>
76773  * 
76774  * <p>Note that session storage is different to local storage (see {@link Ext.data.proxy.LocalStorage}) - if a browser
76775  * session is ended (e.g. by closing the browser) then all data in a SessionStorageProxy are lost. Browser restarts
76776  * don't affect the {@link Ext.data.proxy.LocalStorage} - the data are preserved.</p>
76777  */
76778 Ext.define('Ext.data.proxy.SessionStorage', {
76779     extend: 'Ext.data.proxy.WebStorage',
76780     alias: 'proxy.sessionstorage',
76781     alternateClassName: 'Ext.data.SessionStorageProxy',
76782     
76783     //inherit docs
76784     getStorageObject: function() {
76785         return window.sessionStorage;
76786     }
76787 });
76788
76789 /**
76790  * @author Ed Spencer
76791  * @class Ext.data.reader.Array
76792  * @extends Ext.data.reader.Json
76793  * 
76794  * <p>Data reader class to create an Array of {@link Ext.data.Model} objects from an Array.
76795  * Each element of that Array represents a row of data fields. The
76796  * fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
76797  * of the field definition if it exists, or the field's ordinal position in the definition.</p>
76798  * 
76799  * <p><u>Example code:</u></p>
76800  * 
76801 <pre><code>
76802 Employee = Ext.define('Employee', {
76803     extend: 'Ext.data.Model',
76804     fields: [
76805         'id',
76806         {name: 'name', mapping: 1},         // "mapping" only needed if an "id" field is present which
76807         {name: 'occupation', mapping: 2}    // precludes using the ordinal position as the index.        
76808     ]
76809 });
76810
76811 var myReader = new Ext.data.reader.Array({
76812     model: 'Employee'
76813 }, Employee);
76814 </code></pre>
76815  * 
76816  * <p>This would consume an Array like this:</p>
76817  * 
76818 <pre><code>
76819 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
76820 </code></pre>
76821  * 
76822  * @constructor
76823  * Create a new ArrayReader
76824  * @param {Object} meta Metadata configuration options.
76825  */
76826 Ext.define('Ext.data.reader.Array', {
76827     extend: 'Ext.data.reader.Json',
76828     alternateClassName: 'Ext.data.ArrayReader',
76829     alias : 'reader.array',
76830
76831     /**
76832      * @private
76833      * Most of the work is done for us by JsonReader, but we need to overwrite the field accessors to just
76834      * reference the correct position in the array.
76835      */
76836     buildExtractors: function() {
76837         this.callParent(arguments);
76838         
76839         var fields = this.model.prototype.fields.items,
76840             length = fields.length,
76841             extractorFunctions = [],
76842             i;
76843         
76844         for (i = 0; i < length; i++) {
76845             extractorFunctions.push(function(index) {
76846                 return function(data) {
76847                     return data[index];
76848                 };
76849             }(fields[i].mapping || i));
76850         }
76851         
76852         this.extractorFunctions = extractorFunctions;
76853     }
76854 });
76855
76856 /**
76857  * @author Ed Spencer
76858  * @class Ext.data.reader.Xml
76859  * @extends Ext.data.reader.Reader
76860  * 
76861  * <p>The XML Reader is used by a Proxy to read a server response that is sent back in XML format. This usually
76862  * happens as a result of loading a Store - for example we might create something like this:</p>
76863  * 
76864 <pre><code>
76865 Ext.define('User', {
76866     extend: 'Ext.data.Model',
76867     fields: ['id', 'name', 'email']
76868 });
76869
76870 var store = new Ext.data.Store({
76871     model: 'User',
76872     proxy: {
76873         type: 'ajax',
76874         url : 'users.xml',
76875         reader: {
76876             type: 'xml',
76877             record: 'user'
76878         }
76879     }
76880 });
76881 </code></pre>
76882  * 
76883  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
76884  * not already familiar with them.</p>
76885  * 
76886  * <p>We created the simplest type of XML Reader possible by simply telling our {@link Ext.data.Store Store}'s 
76887  * {@link Ext.data.proxy.Proxy Proxy} that we want a XML Reader. The Store automatically passes the configured model to the
76888  * Store, so it is as if we passed this instead:
76889  * 
76890 <pre><code>
76891 reader: {
76892     type : 'xml',
76893     model: 'User',
76894     record: 'user'
76895 }
76896 </code></pre>
76897  * 
76898  * <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>
76899  *
76900 <pre><code>
76901 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
76902 &lt;user&gt;
76903     &lt;id&gt;1&lt;/id&gt;
76904     &lt;name&gt;Ed Spencer&lt;/name&gt;
76905     &lt;email&gt;ed@sencha.com&lt;/email&gt;
76906 &lt;/user&gt;
76907 &lt;user&gt;
76908     &lt;id&gt;2&lt;/id&gt;
76909     &lt;name&gt;Abe Elias&lt;/name&gt;
76910     &lt;email&gt;abe@sencha.com&lt;/email&gt;
76911 &lt;/user&gt;
76912 </code></pre>
76913  * 
76914  * <p>The XML Reader uses the configured {@link #record} option to pull out the data for each record - in this case we
76915  * set record to 'user', so each &lt;user&gt; above will be converted into a User model.</p>
76916  * 
76917  * <p><u>Reading other XML formats</u></p>
76918  * 
76919  * <p>If you already have your XML format defined and it doesn't look quite like what we have above, you can usually
76920  * pass XmlReader a couple of configuration options to make it parse your format. For example, we can use the 
76921  * {@link #root} configuration to parse data that comes back like this:</p>
76922  * 
76923 <pre><code>
76924 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
76925 &lt;users&gt;
76926     &lt;user&gt;
76927         &lt;id&gt;1&lt;/id&gt;
76928         &lt;name&gt;Ed Spencer&lt;/name&gt;
76929         &lt;email&gt;ed@sencha.com&lt;/email&gt;
76930     &lt;/user&gt;
76931     &lt;user&gt;
76932         &lt;id&gt;2&lt;/id&gt;
76933         &lt;name&gt;Abe Elias&lt;/name&gt;
76934         &lt;email&gt;abe@sencha.com&lt;/email&gt;
76935     &lt;/user&gt;
76936 &lt;/users&gt;
76937 </code></pre>
76938  * 
76939  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
76940  * 
76941 <pre><code>
76942 reader: {
76943     type  : 'xml',
76944     root  : 'users',
76945     record: 'user'
76946 }
76947 </code></pre>
76948  * 
76949  * <p>Note that XmlReader doesn't care whether your {@link #root} and {@link #record} elements are nested deep inside
76950  * a larger structure, so a response like this will still work:
76951  * 
76952 <pre><code>
76953 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
76954 &lt;deeply&gt;
76955     &lt;nested&gt;
76956         &lt;xml&gt;
76957             &lt;users&gt;
76958                 &lt;user&gt;
76959                     &lt;id&gt;1&lt;/id&gt;
76960                     &lt;name&gt;Ed Spencer&lt;/name&gt;
76961                     &lt;email&gt;ed@sencha.com&lt;/email&gt;
76962                 &lt;/user&gt;
76963                 &lt;user&gt;
76964                     &lt;id&gt;2&lt;/id&gt;
76965                     &lt;name&gt;Abe Elias&lt;/name&gt;
76966                     &lt;email&gt;abe@sencha.com&lt;/email&gt;
76967                 &lt;/user&gt;
76968             &lt;/users&gt;
76969         &lt;/xml&gt;
76970     &lt;/nested&gt;
76971 &lt;/deeply&gt;
76972 </code></pre>
76973  * 
76974  * <p><u>Response metadata</u></p>
76975  * 
76976  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records} 
76977  * and the {@link #successProperty success status of the response}. These are typically included in the XML response
76978  * like this:</p>
76979  * 
76980 <pre><code>
76981 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
76982 &lt;total&gt;100&lt;/total&gt;
76983 &lt;success&gt;true&lt;/success&gt;
76984 &lt;users&gt;
76985     &lt;user&gt;
76986         &lt;id&gt;1&lt;/id&gt;
76987         &lt;name&gt;Ed Spencer&lt;/name&gt;
76988         &lt;email&gt;ed@sencha.com&lt;/email&gt;
76989     &lt;/user&gt;
76990     &lt;user&gt;
76991         &lt;id&gt;2&lt;/id&gt;
76992         &lt;name&gt;Abe Elias&lt;/name&gt;
76993         &lt;email&gt;abe@sencha.com&lt;/email&gt;
76994     &lt;/user&gt;
76995 &lt;/users&gt;
76996 </code></pre>
76997  * 
76998  * <p>If these properties are present in the XML response they can be parsed out by the XmlReader and used by the
76999  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration 
77000  * options:</p>
77001  * 
77002 <pre><code>
77003 reader: {
77004     type: 'xml',
77005     root: 'users',
77006     totalProperty  : 'total',
77007     successProperty: 'success'
77008 }
77009 </code></pre>
77010  * 
77011  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
77012  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
77013  * returned.</p>
77014  * 
77015  * <p><u>Response format</u></p>
77016  * 
77017  * <p><b>Note:</b> in order for the browser to parse a returned XML document, the Content-Type header in the HTTP 
77018  * response must be set to "text/xml" or "application/xml". This is very important - the XmlReader will not
77019  * work correctly otherwise.</p>
77020  */
77021 Ext.define('Ext.data.reader.Xml', {
77022     extend: 'Ext.data.reader.Reader',
77023     alternateClassName: 'Ext.data.XmlReader',
77024     alias : 'reader.xml',
77025     
77026     /**
77027      * @cfg {String} record The DomQuery path to the repeated element which contains record information.
77028      */
77029
77030     /**
77031      * @private
77032      * Creates a function to return some particular key of data from a response. The totalProperty and
77033      * successProperty are treated as special cases for type casting, everything else is just a simple selector.
77034      * @param {String} key
77035      * @return {Function}
77036      */
77037     createAccessor: function(expr) {
77038         var me = this;
77039         
77040         if (Ext.isEmpty(expr)) {
77041             return Ext.emptyFn;
77042         }
77043         
77044         if (Ext.isFunction(expr)) {
77045             return expr;
77046         }
77047         
77048         return function(root) {
77049             var node = Ext.DomQuery.selectNode(expr, root),
77050                 val = me.getNodeValue(node);
77051                 
77052             return Ext.isEmpty(val) ? null : val;
77053         };
77054     },
77055     
77056     getNodeValue: function(node) {
77057         var val;
77058         if (node && node.firstChild) {
77059             val = node.firstChild.nodeValue;
77060         }
77061         return val || null;
77062     },
77063
77064     //inherit docs
77065     getResponseData: function(response) {
77066         var xml = response.responseXML;
77067
77068         if (!xml) {
77069             Ext.Error.raise({
77070                 response: response,
77071                 msg: 'XML data not found in the response'
77072             });
77073         }
77074
77075         return xml;
77076     },
77077
77078     /**
77079      * Normalizes the data object
77080      * @param {Object} data The raw data object
77081      * @return {Object} Returns the documentElement property of the data object if present, or the same object if not
77082      */
77083     getData: function(data) {
77084         return data.documentElement || data;
77085     },
77086
77087     /**
77088      * @private
77089      * Given an XML object, returns the Element that represents the root as configured by the Reader's meta data
77090      * @param {Object} data The XML data object
77091      * @return {Element} The root node element
77092      */
77093     getRoot: function(data) {
77094         var nodeName = data.nodeName,
77095             root     = this.root;
77096         
77097         if (!root || (nodeName && nodeName == root)) {
77098             return data;
77099         } else if (Ext.DomQuery.isXml(data)) {
77100             // This fix ensures we have XML data
77101             // Related to TreeStore calling getRoot with the root node, which isn't XML
77102             // Probably should be resolved in TreeStore at some point
77103             return Ext.DomQuery.selectNode(root, data);
77104         }
77105     },
77106
77107     /**
77108      * @private
77109      * We're just preparing the data for the superclass by pulling out the record nodes we want
77110      * @param {Element} root The XML root node
77111      * @return {Array} The records
77112      */
77113     extractData: function(root) {
77114         var recordName = this.record;
77115         
77116         if (!recordName) {
77117             Ext.Error.raise('Record is a required parameter');
77118         }
77119         
77120         if (recordName != root.nodeName) {
77121             root = Ext.DomQuery.select(recordName, root);
77122         } else {
77123             root = [root];
77124         }
77125         return this.callParent([root]);
77126     },
77127     
77128     /**
77129      * @private
77130      * See Ext.data.reader.Reader's getAssociatedDataRoot docs
77131      * @param {Mixed} data The raw data object
77132      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
77133      * @return {Mixed} The root
77134      */
77135     getAssociatedDataRoot: function(data, associationName) {
77136         return Ext.DomQuery.select(associationName, data)[0];
77137     },
77138
77139     /**
77140      * Parses an XML document and returns a ResultSet containing the model instances
77141      * @param {Object} doc Parsed XML document
77142      * @return {Ext.data.ResultSet} The parsed result set
77143      */
77144     readRecords: function(doc) {
77145         //it's possible that we get passed an array here by associations. Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
77146         if (Ext.isArray(doc)) {
77147             doc = doc[0];
77148         }
77149         
77150         /**
77151          * DEPRECATED - will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
77152          * @property xmlData
77153          * @type Object
77154          */
77155         this.xmlData = doc;
77156         return this.callParent([doc]);
77157     }
77158 });
77159
77160 /**
77161  * @author Ed Spencer
77162  * @class Ext.data.writer.Xml
77163  * @extends Ext.data.writer.Writer
77164
77165 This class is used to write {@link Ext.data.Model} data to the server in an XML format.
77166 The {@link #documentRoot} property is used to specify the root element in the XML document.
77167 The {@link #record} option is used to specify the element name for each record that will make
77168 up the XML document.
77169
77170  * @markdown
77171  */
77172 Ext.define('Ext.data.writer.Xml', {
77173     
77174     /* Begin Definitions */
77175     
77176     extend: 'Ext.data.writer.Writer',
77177     alternateClassName: 'Ext.data.XmlWriter',
77178     
77179     alias: 'writer.xml',
77180     
77181     /* End Definitions */
77182     
77183     /**
77184      * @cfg {String} documentRoot The name of the root element of the document. Defaults to <tt>'xmlData'</tt>.
77185      * If there is more than 1 record and the root is not specified, the default document root will still be used
77186      * to ensure a valid XML document is created.
77187      */
77188     documentRoot: 'xmlData',
77189     
77190     /**
77191      * @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty and a root is required
77192      * to form a valid XML document.
77193      */
77194     defaultDocumentRoot: 'xmlData',
77195
77196     /**
77197      * @cfg {String} header A header to use in the XML document (such as setting the encoding or version).
77198      * Defaults to <tt>''</tt>.
77199      */
77200     header: '',
77201
77202     /**
77203      * @cfg {String} record The name of the node to use for each record. Defaults to <tt>'record'</tt>.
77204      */
77205     record: 'record',
77206
77207     //inherit docs
77208     writeRecords: function(request, data) {
77209         var me = this,
77210             xml = [],
77211             i = 0,
77212             len = data.length,
77213             root = me.documentRoot,
77214             record = me.record,
77215             needsRoot = data.length !== 1,
77216             item,
77217             key;
77218             
77219         // may not exist
77220         xml.push(me.header || '');
77221         
77222         if (!root && needsRoot) {
77223             root = me.defaultDocumentRoot;
77224         }
77225         
77226         if (root) {
77227             xml.push('<', root, '>');
77228         }
77229             
77230         for (; i < len; ++i) {
77231             item = data[i];
77232             xml.push('<', record, '>');
77233             for (key in item) {
77234                 if (item.hasOwnProperty(key)) {
77235                     xml.push('<', key, '>', item[key], '</', key, '>');
77236                 }
77237             }
77238             xml.push('</', record, '>');
77239         }
77240         
77241         if (root) {
77242             xml.push('</', root, '>');
77243         }
77244             
77245         request.xmlData = xml.join('');
77246         return request;
77247     }
77248 });
77249
77250 /**
77251  * @class Ext.direct.Event
77252  * A base class for all Ext.direct events. An event is
77253  * created after some kind of interaction with the server.
77254  * The event class is essentially just a data structure
77255  * to hold a direct response.
77256  * 
77257  * @constructor
77258  * @param {Object} config The config object
77259  */
77260 Ext.define('Ext.direct.Event', {
77261     
77262     /* Begin Definitions */
77263    
77264     alias: 'direct.event',
77265     
77266     requires: ['Ext.direct.Manager'],
77267     
77268     /* End Definitions */
77269    
77270     status: true,
77271     
77272     constructor: function(config) {
77273         Ext.apply(this, config);
77274     },
77275     
77276     /**
77277      * Return the raw data for this event.
77278      * @return {Object} The data from the event
77279      */
77280     getData: function(){
77281         return this.data;
77282     }
77283 });
77284
77285 /**
77286  * @class Ext.direct.RemotingEvent
77287  * @extends Ext.direct.Event
77288  * An event that is fired when data is received from a 
77289  * {@link Ext.direct.RemotingProvider}. Contains a method to the
77290  * related transaction for the direct request, see {@link #getTransaction}
77291  */
77292 Ext.define('Ext.direct.RemotingEvent', {
77293     
77294     /* Begin Definitions */
77295    
77296     extend: 'Ext.direct.Event',
77297     
77298     alias: 'direct.rpc',
77299     
77300     /* End Definitions */
77301     
77302     /**
77303      * Get the transaction associated with this event.
77304      * @return {Ext.direct.Transaction} The transaction
77305      */
77306     getTransaction: function(){
77307         return this.transaction || Ext.direct.Manager.getTransaction(this.tid);
77308     }
77309 });
77310
77311 /**
77312  * @class Ext.direct.ExceptionEvent
77313  * @extends Ext.direct.RemotingEvent
77314  * An event that is fired when an exception is received from a {@link Ext.direct.RemotingProvider}
77315  */
77316 Ext.define('Ext.direct.ExceptionEvent', {
77317     
77318     /* Begin Definitions */
77319    
77320     extend: 'Ext.direct.RemotingEvent',
77321     
77322     alias: 'direct.exception',
77323     
77324     /* End Definitions */
77325    
77326    status: false
77327 });
77328
77329 /**
77330  * @class Ext.direct.Provider
77331  * <p>Ext.direct.Provider is an abstract class meant to be extended.</p>
77332  * 
77333  * <p>For example ExtJs implements the following subclasses:</p>
77334  * <pre><code>
77335 Provider
77336 |
77337 +---{@link Ext.direct.JsonProvider JsonProvider} 
77338     |
77339     +---{@link Ext.direct.PollingProvider PollingProvider}   
77340     |
77341     +---{@link Ext.direct.RemotingProvider RemotingProvider}   
77342  * </code></pre>
77343  * @abstract
77344  */
77345 Ext.define('Ext.direct.Provider', {
77346     
77347     /* Begin Definitions */
77348    
77349    alias: 'direct.provider',
77350    
77351     mixins: {
77352         observable: 'Ext.util.Observable'   
77353     },
77354    
77355     /* End Definitions */
77356    
77357    /**
77358      * @cfg {String} id
77359      * The unique id of the provider (defaults to an {@link Ext#id auto-assigned id}).
77360      * You should assign an id if you need to be able to access the provider later and you do
77361      * not have an object reference available, for example:
77362      * <pre><code>
77363 Ext.direct.Manager.addProvider({
77364     type: 'polling',
77365     url:  'php/poll.php',
77366     id:   'poll-provider'
77367 });     
77368 var p = {@link Ext.direct.Manager}.{@link Ext.direct.Manager#getProvider getProvider}('poll-provider');
77369 p.disconnect();
77370      * </code></pre>
77371      */
77372     
77373     constructor : function(config){
77374         var me = this;
77375         
77376         Ext.apply(me, config);
77377         me.addEvents(
77378             /**
77379              * @event connect
77380              * Fires when the Provider connects to the server-side
77381              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
77382              */            
77383             'connect',
77384             /**
77385              * @event disconnect
77386              * Fires when the Provider disconnects from the server-side
77387              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
77388              */            
77389             'disconnect',
77390             /**
77391              * @event data
77392              * Fires when the Provider receives data from the server-side
77393              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
77394              * @param {event} e The Ext.Direct.Event type that occurred.
77395              */            
77396             'data',
77397             /**
77398              * @event exception
77399              * Fires when the Provider receives an exception from the server-side
77400              */                        
77401             'exception'
77402         );
77403         me.mixins.observable.constructor.call(me, config);
77404     },
77405     
77406     /**
77407      * Returns whether or not the server-side is currently connected.
77408      * Abstract method for subclasses to implement.
77409      */
77410     isConnected: function(){
77411         return false;
77412     },
77413
77414     /**
77415      * Abstract methods for subclasses to implement.
77416      * @method
77417      */
77418     connect: Ext.emptyFn,
77419     
77420     /**
77421      * Abstract methods for subclasses to implement.
77422      * @method
77423      */
77424     disconnect: Ext.emptyFn
77425 });
77426
77427 /**
77428  * @class Ext.direct.JsonProvider
77429  * @extends Ext.direct.Provider
77430
77431 A base provider for communicating using JSON. This is an abstract class
77432 and should not be instanced directly.
77433
77434  * @markdown
77435  * @abstract
77436  */
77437
77438 Ext.define('Ext.direct.JsonProvider', {
77439     
77440     /* Begin Definitions */
77441     
77442     extend: 'Ext.direct.Provider',
77443     
77444     alias: 'direct.jsonprovider',
77445     
77446     uses: ['Ext.direct.ExceptionEvent'],
77447     
77448     /* End Definitions */
77449    
77450    /**
77451     * Parse the JSON response
77452     * @private
77453     * @param {Object} response The XHR response object
77454     * @return {Object} The data in the response.
77455     */
77456    parseResponse: function(response){
77457         if (!Ext.isEmpty(response.responseText)) {
77458             if (Ext.isObject(response.responseText)) {
77459                 return response.responseText;
77460             }
77461             return Ext.decode(response.responseText);
77462         }
77463         return null;
77464     },
77465
77466     /**
77467      * Creates a set of events based on the XHR response
77468      * @private
77469      * @param {Object} response The XHR response
77470      * @return {Array} An array of Ext.direct.Event
77471      */
77472     createEvents: function(response){
77473         var data = null,
77474             events = [],
77475             event,
77476             i = 0,
77477             len;
77478             
77479         try{
77480             data = this.parseResponse(response);
77481         } catch(e) {
77482             event = Ext.create('Ext.direct.ExceptionEvent', {
77483                 data: e,
77484                 xhr: response,
77485                 code: Ext.direct.Manager.self.exceptions.PARSE,
77486                 message: 'Error parsing json response: \n\n ' + data
77487             });
77488             return [event];
77489         }
77490         
77491         if (Ext.isArray(data)) {
77492             for (len = data.length; i < len; ++i) {
77493                 events.push(this.createEvent(data[i]));
77494             }
77495         } else {
77496             events.push(this.createEvent(data));
77497         }
77498         return events;
77499     },
77500     
77501     /**
77502      * Create an event from a response object
77503      * @param {Object} response The XHR response object
77504      * @return {Ext.direct.Event} The event
77505      */
77506     createEvent: function(response){
77507         return Ext.create('direct.' + response.type, response);
77508     }
77509 });
77510 /**
77511  * @class Ext.direct.PollingProvider
77512  * @extends Ext.direct.JsonProvider
77513  *
77514  * <p>Provides for repetitive polling of the server at distinct {@link #interval intervals}.
77515  * The initial request for data originates from the client, and then is responded to by the
77516  * server.</p>
77517  * 
77518  * <p>All configurations for the PollingProvider should be generated by the server-side
77519  * API portion of the Ext.Direct stack.</p>
77520  *
77521  * <p>An instance of PollingProvider may be created directly via the new keyword or by simply
77522  * specifying <tt>type = 'polling'</tt>.  For example:</p>
77523  * <pre><code>
77524 var pollA = new Ext.direct.PollingProvider({
77525     type:'polling',
77526     url: 'php/pollA.php',
77527 });
77528 Ext.direct.Manager.addProvider(pollA);
77529 pollA.disconnect();
77530
77531 Ext.direct.Manager.addProvider(
77532     {
77533         type:'polling',
77534         url: 'php/pollB.php',
77535         id: 'pollB-provider'
77536     }
77537 );
77538 var pollB = Ext.direct.Manager.getProvider('pollB-provider');
77539  * </code></pre>
77540  */
77541 Ext.define('Ext.direct.PollingProvider', {
77542     
77543     /* Begin Definitions */
77544     
77545     extend: 'Ext.direct.JsonProvider',
77546     
77547     alias: 'direct.pollingprovider',
77548     
77549     uses: ['Ext.direct.ExceptionEvent'],
77550     
77551     requires: ['Ext.Ajax', 'Ext.util.DelayedTask'],
77552     
77553     /* End Definitions */
77554     
77555     /**
77556      * @cfg {Number} interval
77557      * How often to poll the server-side in milliseconds (defaults to <tt>3000</tt> - every
77558      * 3 seconds).
77559      */
77560     interval: 3000,
77561
77562     /**
77563      * @cfg {Object} baseParams An object containing properties which are to be sent as parameters
77564      * on every polling request
77565      */
77566     
77567     /**
77568      * @cfg {String/Function} url
77569      * The url which the PollingProvider should contact with each request. This can also be
77570      * an imported Ext.Direct method which will accept the baseParams as its only argument.
77571      */
77572
77573     // private
77574     constructor : function(config){
77575         this.callParent(arguments);
77576         this.addEvents(
77577             /**
77578              * @event beforepoll
77579              * Fired immediately before a poll takes place, an event handler can return false
77580              * in order to cancel the poll.
77581              * @param {Ext.direct.PollingProvider}
77582              */
77583             'beforepoll',            
77584             /**
77585              * @event poll
77586              * This event has not yet been implemented.
77587              * @param {Ext.direct.PollingProvider}
77588              */
77589             'poll'
77590         );
77591     },
77592
77593     // inherited
77594     isConnected: function(){
77595         return !!this.pollTask;
77596     },
77597
77598     /**
77599      * Connect to the server-side and begin the polling process. To handle each
77600      * response subscribe to the data event.
77601      */
77602     connect: function(){
77603         var me = this, url = me.url;
77604         
77605         if (url && !me.pollTask) {
77606             me.pollTask = Ext.TaskManager.start({
77607                 run: function(){
77608                     if (me.fireEvent('beforepoll', me) !== false) {
77609                         if (Ext.isFunction(url)) {
77610                             url(me.baseParams);
77611                         } else {
77612                             Ext.Ajax.request({
77613                                 url: url,
77614                                 callback: me.onData,
77615                                 scope: me,
77616                                 params: me.baseParams
77617                             });
77618                         }
77619                     }
77620                 },
77621                 interval: me.interval,
77622                 scope: me
77623             });
77624             me.fireEvent('connect', me);
77625         } else if (!url) {
77626             Ext.Error.raise('Error initializing PollingProvider, no url configured.');
77627         }
77628     },
77629
77630     /**
77631      * Disconnect from the server-side and stop the polling process. The disconnect
77632      * event will be fired on a successful disconnect.
77633      */
77634     disconnect: function(){
77635         var me = this;
77636         
77637         if (me.pollTask) {
77638             Ext.TaskManager.stop(me.pollTask);
77639             delete me.pollTask;
77640             me.fireEvent('disconnect', me);
77641         }
77642     },
77643
77644     // private
77645     onData: function(opt, success, response){
77646         var me = this, 
77647             i = 0, 
77648             len,
77649             events;
77650         
77651         if (success) {
77652             events = me.createEvents(response);
77653             for (len = events.length; i < len; ++i) {
77654                 me.fireEvent('data', me, events[i]);
77655             }
77656         } else {
77657             me.fireEvent('data', me, Ext.create('Ext.direct.ExceptionEvent', {
77658                 data: null,
77659                 code: Ext.direct.Manager.self.exceptions.TRANSPORT,
77660                 message: 'Unable to connect to the server.',
77661                 xhr: response
77662             }));
77663         }
77664     }
77665 });
77666 /**
77667  * Small utility class used internally to represent a Direct method.
77668  * Thi class is used internally.
77669  * @class Ext.direct.RemotingMethod
77670  * @ignore
77671  */
77672 Ext.define('Ext.direct.RemotingMethod', {
77673     
77674     constructor: function(config){
77675         var me = this,
77676             params = Ext.isDefined(config.params) ? config.params : config.len,
77677             name;
77678             
77679         me.name = config.name;
77680         me.formHandler = config.formHandler;
77681         if (Ext.isNumber(params)) {
77682             // given only the number of parameters
77683             me.len = params;
77684             me.ordered = true;
77685         } else {
77686             /*
77687              * Given an array of either
77688              * a) String
77689              * b) Objects with a name property. We may want to encode extra info in here later
77690              */
77691             me.params = [];
77692             Ext.each(params, function(param){
77693                 name = Ext.isObject(param) ? param.name : param;
77694                 me.params.push(name);
77695             });
77696         }
77697     },
77698     
77699     /**
77700      * Takes the arguments for the Direct function and splits the arguments
77701      * from the scope and the callback.
77702      * @param {Array} args The arguments passed to the direct call
77703      * @return {Object} An object with 3 properties, args, callback & scope.
77704      */
77705     getCallData: function(args){
77706         var me = this,
77707             data = null,
77708             len  = me.len,
77709             params = me.params,
77710             callback,
77711             scope,
77712             name;
77713             
77714         if (me.ordered) {
77715             callback = args[len];
77716             scope = args[len + 1];
77717             if (len !== 0) {
77718                 data = args.slice(0, len);
77719             }
77720         } else {
77721             data = Ext.apply({}, args[0]);
77722             callback = args[1];
77723             scope = args[2];
77724             
77725             // filter out any non-existent properties
77726             for (name in data) {
77727                 if (data.hasOwnProperty(name)) {
77728                     if (!Ext.Array.contains(params, name)) {
77729                         delete data[name];
77730                     }
77731                 }
77732             }
77733         }
77734         
77735         return {
77736             data: data,
77737             callback: callback,
77738             scope: scope    
77739         };
77740     }
77741 });
77742
77743 /**
77744  * @class Ext.direct.Transaction
77745  * @extends Object
77746  * <p>Supporting Class for Ext.Direct (not intended to be used directly).</p>
77747  * @constructor
77748  * @param {Object} config
77749  */
77750 Ext.define('Ext.direct.Transaction', {
77751     
77752     /* Begin Definitions */
77753    
77754     alias: 'direct.transaction',
77755     alternateClassName: 'Ext.Direct.Transaction',
77756    
77757     statics: {
77758         TRANSACTION_ID: 0
77759     },
77760    
77761     /* End Definitions */
77762    
77763     constructor: function(config){
77764         var me = this;
77765         
77766         Ext.apply(me, config);
77767         me.id = ++me.self.TRANSACTION_ID;
77768         me.retryCount = 0;
77769     },
77770    
77771     send: function(){
77772          this.provider.queueTransaction(this);
77773     },
77774
77775     retry: function(){
77776         this.retryCount++;
77777         this.send();
77778     },
77779
77780     getProvider: function(){
77781         return this.provider;
77782     }
77783 });
77784
77785 /**
77786  * @class Ext.direct.RemotingProvider
77787  * @extends Ext.direct.JsonProvider
77788  * 
77789  * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
77790  * server side methods on the client (a remote procedure call (RPC) type of
77791  * connection where the client can initiate a procedure on the server).</p>
77792  * 
77793  * <p>This allows for code to be organized in a fashion that is maintainable,
77794  * while providing a clear path between client and server, something that is
77795  * not always apparent when using URLs.</p>
77796  * 
77797  * <p>To accomplish this the server-side needs to describe what classes and methods
77798  * are available on the client-side. This configuration will typically be
77799  * outputted by the server-side Ext.Direct stack when the API description is built.</p>
77800  */
77801 Ext.define('Ext.direct.RemotingProvider', {
77802     
77803     /* Begin Definitions */
77804    
77805     alias: 'direct.remotingprovider',
77806     
77807     extend: 'Ext.direct.JsonProvider', 
77808     
77809     requires: [
77810         'Ext.util.MixedCollection', 
77811         'Ext.util.DelayedTask', 
77812         'Ext.direct.Transaction',
77813         'Ext.direct.RemotingMethod'
77814     ],
77815    
77816     /* End Definitions */
77817    
77818    /**
77819      * @cfg {Object} actions
77820      * Object literal defining the server side actions and methods. For example, if
77821      * the Provider is configured with:
77822      * <pre><code>
77823 "actions":{ // each property within the 'actions' object represents a server side Class 
77824     "TestAction":[ // array of methods within each server side Class to be   
77825     {              // stubbed out on client
77826         "name":"doEcho", 
77827         "len":1            
77828     },{
77829         "name":"multiply",// name of method
77830         "len":2           // The number of parameters that will be used to create an
77831                           // array of data to send to the server side function.
77832                           // Ensure the server sends back a Number, not a String. 
77833     },{
77834         "name":"doForm",
77835         "formHandler":true, // direct the client to use specialized form handling method 
77836         "len":1
77837     }]
77838 }
77839      * </code></pre>
77840      * <p>Note that a Store is not required, a server method can be called at any time.
77841      * In the following example a <b>client side</b> handler is used to call the
77842      * server side method "multiply" in the server-side "TestAction" Class:</p>
77843      * <pre><code>
77844 TestAction.multiply(
77845     2, 4, // pass two arguments to server, so specify len=2
77846     // callback function after the server is called
77847     // result: the result returned by the server
77848     //      e: Ext.direct.RemotingEvent object
77849     function(result, e){
77850         var t = e.getTransaction();
77851         var action = t.action; // server side Class called
77852         var method = t.method; // server side method called
77853         if(e.status){
77854             var answer = Ext.encode(result); // 8
77855     
77856         }else{
77857             var msg = e.message; // failure message
77858         }
77859     }
77860 );
77861      * </code></pre>
77862      * In the example above, the server side "multiply" function will be passed two
77863      * arguments (2 and 4).  The "multiply" method should return the value 8 which will be
77864      * available as the <tt>result</tt> in the example above. 
77865      */
77866     
77867     /**
77868      * @cfg {String/Object} namespace
77869      * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
77870      * Explicitly specify the namespace Object, or specify a String to have a
77871      * {@link Ext#namespace namespace created} implicitly.
77872      */
77873     
77874     /**
77875      * @cfg {String} url
77876      * <b>Required</b>. The url to connect to the {@link Ext.direct.Manager} server-side router. 
77877      */
77878     
77879     /**
77880      * @cfg {String} enableUrlEncode
77881      * Specify which param will hold the arguments for the method.
77882      * Defaults to <tt>'data'</tt>.
77883      */
77884     
77885     /**
77886      * @cfg {Number/Boolean} enableBuffer
77887      * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method
77888      * calls. If a number is specified this is the amount of time in milliseconds
77889      * to wait before sending a batched request (defaults to <tt>10</tt>).</p>
77890      * <br><p>Calls which are received within the specified timeframe will be
77891      * concatenated together and sent in a single request, optimizing the
77892      * application by reducing the amount of round trips that have to be made
77893      * to the server.</p>
77894      */
77895     enableBuffer: 10,
77896     
77897     /**
77898      * @cfg {Number} maxRetries
77899      * Number of times to re-attempt delivery on failure of a call. Defaults to <tt>1</tt>.
77900      */
77901     maxRetries: 1,
77902     
77903     /**
77904      * @cfg {Number} timeout
77905      * The timeout to use for each request. Defaults to <tt>undefined</tt>.
77906      */
77907     timeout: undefined,
77908     
77909     constructor : function(config){
77910         var me = this;
77911         me.callParent(arguments);
77912         me.addEvents(
77913             /**
77914              * @event beforecall
77915              * Fires immediately before the client-side sends off the RPC call.
77916              * By returning false from an event handler you can prevent the call from
77917              * executing.
77918              * @param {Ext.direct.RemotingProvider} provider
77919              * @param {Ext.direct.Transaction} transaction
77920              * @param {Object} meta The meta data
77921              */            
77922             'beforecall',            
77923             /**
77924              * @event call
77925              * Fires immediately after the request to the server-side is sent. This does
77926              * NOT fire after the response has come back from the call.
77927              * @param {Ext.direct.RemotingProvider} provider
77928              * @param {Ext.direct.Transaction} transaction
77929              * @param {Object} meta The meta data
77930              */            
77931             'call'
77932         );
77933         me.namespace = (Ext.isString(me.namespace)) ? Ext.ns(me.namespace) : me.namespace || window;
77934         me.transactions = Ext.create('Ext.util.MixedCollection');
77935         me.callBuffer = [];
77936     },
77937     
77938     /**
77939      * Initialize the API
77940      * @private
77941      */
77942     initAPI : function(){
77943         var actions = this.actions,
77944             namespace = this.namespace,
77945             action,
77946             cls,
77947             methods,
77948             i,
77949             len,
77950             method;
77951             
77952         for (action in actions) {
77953             cls = namespace[action];
77954             if (!cls) {
77955                 cls = namespace[action] = {};
77956             }
77957             methods = actions[action];
77958             
77959             for (i = 0, len = methods.length; i < len; ++i) {
77960                 method = Ext.create('Ext.direct.RemotingMethod', methods[i]);
77961                 cls[method.name] = this.createHandler(action, method);
77962             }
77963         }
77964     },
77965     
77966     /**
77967      * Create a handler function for a direct call.
77968      * @private
77969      * @param {String} action The action the call is for
77970      * @param {Object} method The details of the method
77971      * @return {Function} A JS function that will kick off the call
77972      */
77973     createHandler : function(action, method){
77974         var me = this,
77975             handler;
77976         
77977         if (!method.formHandler) {
77978             handler = function(){
77979                 me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
77980             };
77981         } else {
77982             handler = function(form, callback, scope){
77983                 me.configureFormRequest(action, method, form, callback, scope);
77984             };
77985         }
77986         handler.directCfg = {
77987             action: action,
77988             method: method
77989         };
77990         return handler;
77991     },
77992     
77993     // inherit docs
77994     isConnected: function(){
77995         return !!this.connected;
77996     },
77997
77998     // inherit docs
77999     connect: function(){
78000         var me = this;
78001         
78002         if (me.url) {
78003             me.initAPI();
78004             me.connected = true;
78005             me.fireEvent('connect', me);
78006         } else if(!me.url) {
78007             Ext.Error.raise('Error initializing RemotingProvider, no url configured.');
78008         }
78009     },
78010
78011     // inherit docs
78012     disconnect: function(){
78013         var me = this;
78014         
78015         if (me.connected) {
78016             me.connected = false;
78017             me.fireEvent('disconnect', me);
78018         }
78019     },
78020     
78021     /**
78022      * Run any callbacks related to the transaction.
78023      * @private
78024      * @param {Ext.direct.Transaction} transaction The transaction
78025      * @param {Ext.direct.Event} event The event
78026      */
78027     runCallback: function(transaction, event){
78028         var funcName = event.status ? 'success' : 'failure',
78029             callback,
78030             result;
78031         
78032         if (transaction && transaction.callback) {
78033             callback = transaction.callback;
78034             result = Ext.isDefined(event.result) ? event.result : event.data;
78035         
78036             if (Ext.isFunction(callback)) {
78037                 callback(result, event);
78038             } else {
78039                 Ext.callback(callback[funcName], callback.scope, [result, event]);
78040                 Ext.callback(callback.callback, callback.scope, [result, event]);
78041             }
78042         }
78043     },
78044     
78045     /**
78046      * React to the ajax request being completed
78047      * @private
78048      */
78049     onData: function(options, success, response){
78050         var me = this,
78051             i = 0,
78052             len,
78053             events,
78054             event,
78055             transaction,
78056             transactions;
78057             
78058         if (success) {
78059             events = me.createEvents(response);
78060             for (len = events.length; i < len; ++i) {
78061                 event = events[i];
78062                 transaction = me.getTransaction(event);
78063                 me.fireEvent('data', me, event);
78064                 if (transaction) {
78065                     me.runCallback(transaction, event, true);
78066                     Ext.direct.Manager.removeTransaction(transaction);
78067                 }
78068             }
78069         } else {
78070             transactions = [].concat(options.transaction);
78071             for (len = transactions.length; i < len; ++i) {
78072                 transaction = me.getTransaction(transactions[i]);
78073                 if (transaction && transaction.retryCount < me.maxRetries) {
78074                     transaction.retry();
78075                 } else {
78076                     event = Ext.create('Ext.direct.ExceptionEvent', {
78077                         data: null,
78078                         transaction: transaction,
78079                         code: Ext.direct.Manager.self.exceptions.TRANSPORT,
78080                         message: 'Unable to connect to the server.',
78081                         xhr: response
78082                     });
78083                     me.fireEvent('data', me, event);
78084                     if (transaction) {
78085                         me.runCallback(transaction, event, false);
78086                         Ext.direct.Manager.removeTransaction(transaction);
78087                     }
78088                 }
78089             }
78090         }
78091     },
78092     
78093     /**
78094      * Get transaction from XHR options
78095      * @private
78096      * @param {Object} options The options sent to the Ajax request
78097      * @return {Ext.direct.Transaction} The transaction, null if not found
78098      */
78099     getTransaction: function(options){
78100         return options && options.tid ? Ext.direct.Manager.getTransaction(options.tid) : null;
78101     },
78102     
78103     /**
78104      * Configure a direct request
78105      * @private
78106      * @param {String} action The action being executed
78107      * @param {Object} method The being executed
78108      */
78109     configureRequest: function(action, method, args){
78110         var me = this,
78111             callData = method.getCallData(args),
78112             data = callData.data, 
78113             callback = callData.callback, 
78114             scope = callData.scope,
78115             transaction;
78116
78117         transaction = Ext.create('Ext.direct.Transaction', {
78118             provider: me,
78119             args: args,
78120             action: action,
78121             method: method.name,
78122             data: data,
78123             callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
78124         });
78125
78126         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
78127             Ext.direct.Manager.addTransaction(transaction);
78128             me.queueTransaction(transaction);
78129             me.fireEvent('call', me, transaction, method);
78130         }
78131     },
78132     
78133     /**
78134      * Gets the Ajax call info for a transaction
78135      * @private
78136      * @param {Ext.direct.Transaction} transaction The transaction
78137      * @return {Object} The call params
78138      */
78139     getCallData: function(transaction){
78140         return {
78141             action: transaction.action,
78142             method: transaction.method,
78143             data: transaction.data,
78144             type: 'rpc',
78145             tid: transaction.id
78146         };
78147     },
78148     
78149     /**
78150      * Sends a request to the server
78151      * @private
78152      * @param {Object/Array} data The data to send
78153      */
78154     sendRequest : function(data){
78155         var me = this,
78156             request = {
78157                 url: me.url,
78158                 callback: me.onData,
78159                 scope: me,
78160                 transaction: data,
78161                 timeout: me.timeout
78162             }, callData,
78163             enableUrlEncode = me.enableUrlEncode,
78164             i = 0,
78165             len,
78166             params;
78167             
78168
78169         if (Ext.isArray(data)) {
78170             callData = [];
78171             for (len = data.length; i < len; ++i) {
78172                 callData.push(me.getCallData(data[i]));
78173             }
78174         } else {
78175             callData = me.getCallData(data);
78176         }
78177
78178         if (enableUrlEncode) {
78179             params = {};
78180             params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
78181             request.params = params;
78182         } else {
78183             request.jsonData = callData;
78184         }
78185         Ext.Ajax.request(request);
78186     },
78187     
78188     /**
78189      * Add a new transaction to the queue
78190      * @private
78191      * @param {Ext.direct.Transaction} transaction The transaction
78192      */
78193     queueTransaction: function(transaction){
78194         var me = this,
78195             enableBuffer = me.enableBuffer;
78196         
78197         if (transaction.form) {
78198             me.sendFormRequest(transaction);
78199             return;
78200         }
78201         
78202         me.callBuffer.push(transaction);
78203         if (enableBuffer) {
78204             if (!me.callTask) {
78205                 me.callTask = Ext.create('Ext.util.DelayedTask', me.combineAndSend, me);
78206             }
78207             me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
78208         } else {
78209             me.combineAndSend();
78210         }
78211     },
78212     
78213     /**
78214      * Combine any buffered requests and send them off
78215      * @private
78216      */
78217     combineAndSend : function(){
78218         var buffer = this.callBuffer,
78219             len = buffer.length;
78220             
78221         if (len > 0) {
78222             this.sendRequest(len == 1 ? buffer[0] : buffer);
78223             this.callBuffer = [];
78224         }
78225     },
78226     
78227     /**
78228      * Configure a form submission request
78229      * @private
78230      * @param {String} action The action being executed
78231      * @param {Object} method The method being executed
78232      * @param {HTMLElement} form The form being submitted
78233      * @param {Function} callback (optional) A callback to run after the form submits
78234      * @param {Object} scope A scope to execute the callback in
78235      */
78236     configureFormRequest : function(action, method, form, callback, scope){
78237         var me = this,
78238             transaction = Ext.create('Ext.direct.Transaction', {
78239                 provider: me,
78240                 action: action,
78241                 method: method.name,
78242                 args: [form, callback, scope],
78243                 callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
78244                 isForm: true
78245             }),
78246             isUpload,
78247             params;
78248
78249         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
78250             Ext.direct.Manager.addTransaction(transaction);
78251             isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
78252             
78253             params = {
78254                 extTID: transaction.id,
78255                 extAction: action,
78256                 extMethod: method.name,
78257                 extType: 'rpc',
78258                 extUpload: String(isUpload)
78259             };
78260             
78261             // change made from typeof callback check to callback.params
78262             // to support addl param passing in DirectSubmit EAC 6/2
78263             Ext.apply(transaction, {
78264                 form: Ext.getDom(form),
78265                 isUpload: isUpload,
78266                 params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
78267             });
78268             me.fireEvent('call', me, transaction, method);
78269             me.sendFormRequest(transaction);
78270         }
78271     },
78272     
78273     /**
78274      * Sends a form request
78275      * @private
78276      * @param {Ext.direct.Transaction} transaction The transaction to send
78277      */
78278     sendFormRequest: function(transaction){
78279         Ext.Ajax.request({
78280             url: this.url,
78281             params: transaction.params,
78282             callback: this.onData,
78283             scope: this,
78284             form: transaction.form,
78285             isUpload: transaction.isUpload,
78286             transaction: transaction
78287         });
78288     }
78289     
78290 });
78291
78292 /*
78293  * @class Ext.draw.Matrix
78294  * @private
78295  */
78296 Ext.define('Ext.draw.Matrix', {
78297
78298     /* Begin Definitions */
78299
78300     requires: ['Ext.draw.Draw'],
78301
78302     /* End Definitions */
78303
78304     constructor: function(a, b, c, d, e, f) {
78305         if (a != null) {
78306             this.matrix = [[a, c, e], [b, d, f], [0, 0, 1]];
78307         }
78308         else {
78309             this.matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
78310         }
78311     },
78312
78313     add: function(a, b, c, d, e, f) {
78314         var me = this,
78315             out = [[], [], []],
78316             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
78317             x,
78318             y,
78319             z,
78320             res;
78321
78322         for (x = 0; x < 3; x++) {
78323             for (y = 0; y < 3; y++) {
78324                 res = 0;
78325                 for (z = 0; z < 3; z++) {
78326                     res += me.matrix[x][z] * matrix[z][y];
78327                 }
78328                 out[x][y] = res;
78329             }
78330         }
78331         me.matrix = out;
78332     },
78333
78334     prepend: function(a, b, c, d, e, f) {
78335         var me = this,
78336             out = [[], [], []],
78337             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
78338             x,
78339             y,
78340             z,
78341             res;
78342
78343         for (x = 0; x < 3; x++) {
78344             for (y = 0; y < 3; y++) {
78345                 res = 0;
78346                 for (z = 0; z < 3; z++) {
78347                     res += matrix[x][z] * me.matrix[z][y];
78348                 }
78349                 out[x][y] = res;
78350             }
78351         }
78352         me.matrix = out;
78353     },
78354
78355     invert: function() {
78356         var matrix = this.matrix,
78357             a = matrix[0][0],
78358             b = matrix[1][0],
78359             c = matrix[0][1],
78360             d = matrix[1][1],
78361             e = matrix[0][2],
78362             f = matrix[1][2],
78363             x = a * d - b * c;
78364         return new Ext.draw.Matrix(d / x, -b / x, -c / x, a / x, (c * f - d * e) / x, (b * e - a * f) / x);
78365     },
78366
78367     clone: function() {
78368         var matrix = this.matrix,
78369             a = matrix[0][0],
78370             b = matrix[1][0],
78371             c = matrix[0][1],
78372             d = matrix[1][1],
78373             e = matrix[0][2],
78374             f = matrix[1][2];
78375         return new Ext.draw.Matrix(a, b, c, d, e, f);
78376     },
78377
78378     translate: function(x, y) {
78379         this.prepend(1, 0, 0, 1, x, y);
78380     },
78381
78382     scale: function(x, y, cx, cy) {
78383         var me = this;
78384         if (y == null) {
78385             y = x;
78386         }
78387         me.add(1, 0, 0, 1, cx, cy);
78388         me.add(x, 0, 0, y, 0, 0);
78389         me.add(1, 0, 0, 1, -cx, -cy);
78390     },
78391
78392     rotate: function(a, x, y) {
78393         a = Ext.draw.Draw.rad(a);
78394         var me = this,
78395             cos = +Math.cos(a).toFixed(9),
78396             sin = +Math.sin(a).toFixed(9);
78397         me.add(cos, sin, -sin, cos, x, y);
78398         me.add(1, 0, 0, 1, -x, -y);
78399     },
78400
78401     x: function(x, y) {
78402         var matrix = this.matrix;
78403         return x * matrix[0][0] + y * matrix[0][1] + matrix[0][2];
78404     },
78405
78406     y: function(x, y) {
78407         var matrix = this.matrix;
78408         return x * matrix[1][0] + y * matrix[1][1] + matrix[1][2];
78409     },
78410
78411     get: function(i, j) {
78412         return + this.matrix[i][j].toFixed(4);
78413     },
78414
78415     toString: function() {
78416         var me = this;
78417         return [me.get(0, 0), me.get(0, 1), me.get(1, 0), me.get(1, 1), 0, 0].join();
78418     },
78419
78420     toSvg: function() {
78421         var me = this;
78422         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() + ")";
78423     },
78424
78425     toFilter: function() {
78426         var me = this;
78427         return "progid:DXImageTransform.Microsoft.Matrix(M11=" + me.get(0, 0) +
78428             ", M12=" + me.get(0, 1) + ", M21=" + me.get(1, 0) + ", M22=" + me.get(1, 1) +
78429             ", Dx=" + me.get(0, 2) + ", Dy=" + me.get(1, 2) + ")";
78430     },
78431
78432     offset: function() {
78433         var matrix = this.matrix;
78434         return [matrix[0][2].toFixed(4), matrix[1][2].toFixed(4)];
78435     },
78436
78437     // Split matrix into Translate Scale, Shear, and Rotate
78438     split: function () {
78439         function norm(a) {
78440             return a[0] * a[0] + a[1] * a[1];
78441         }
78442         function normalize(a) {
78443             var mag = Math.sqrt(norm(a));
78444             a[0] /= mag;
78445             a[1] /= mag;
78446         }
78447         var matrix = this.matrix,
78448             out = {
78449                 translateX: matrix[0][2],
78450                 translateY: matrix[1][2]
78451             },
78452             row;
78453
78454         // scale and shear
78455         row = [[matrix[0][0], matrix[0][1]], [matrix[1][1], matrix[1][1]]];
78456         out.scaleX = Math.sqrt(norm(row[0]));
78457         normalize(row[0]);
78458
78459         out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
78460         row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
78461
78462         out.scaleY = Math.sqrt(norm(row[1]));
78463         normalize(row[1]);
78464         out.shear /= out.scaleY;
78465
78466         // rotation
78467         out.rotate = Math.asin(-row[0][1]);
78468
78469         out.isSimple = !+out.shear.toFixed(9) && (out.scaleX.toFixed(9) == out.scaleY.toFixed(9) || !out.rotate);
78470
78471         return out;
78472     }
78473 });
78474 // private - DD implementation for Panels
78475 Ext.define('Ext.draw.SpriteDD', {
78476     extend: 'Ext.dd.DragSource',
78477
78478     constructor : function(sprite, cfg){
78479         var me = this,
78480             el = sprite.el;
78481         me.sprite = sprite;
78482         me.el = el;
78483         me.dragData = {el: el, sprite: sprite};
78484         me.callParent([el, cfg]);
78485         me.sprite.setStyle('cursor', 'move');
78486     },
78487
78488     showFrame: Ext.emptyFn,
78489     createFrame : Ext.emptyFn,
78490
78491     getDragEl : function(e){
78492         return this.el;
78493     },
78494     
78495     getRegion: function() {
78496         var me = this,
78497             el = me.el,
78498             pos, x1, x2, y1, y2, t, r, b, l, bbox, sprite;
78499         
78500         sprite = me.sprite;
78501         bbox = sprite.getBBox();
78502         
78503         try {
78504             pos = Ext.core.Element.getXY(el);
78505         } catch (e) { }
78506
78507         if (!pos) {
78508             return null;
78509         }
78510
78511         x1 = pos[0];
78512         x2 = x1 + bbox.width;
78513         y1 = pos[1];
78514         y2 = y1 + bbox.height;
78515         
78516         return Ext.create('Ext.util.Region', y1, x2, y2, x1);
78517     },
78518
78519     /*
78520       TODO(nico): Cumulative translations in VML are handled
78521       differently than in SVG. While in SVG we specify the translation
78522       relative to the original x, y position attributes, in VML the translation
78523       is a delta between the last position of the object (modified by the last
78524       translation) and the new one.
78525       
78526       In VML the translation alters the position
78527       of the object, we should change that or alter the SVG impl.
78528     */
78529      
78530     startDrag: function(x, y) {
78531         var me = this,
78532             attr = me.sprite.attr,
78533             trans = attr.translation;
78534         if (me.sprite.vml) {
78535             me.prevX = x + attr.x;
78536             me.prevY = y + attr.y;
78537         } else {
78538             me.prevX = x - trans.x;
78539             me.prevY = y - trans.y;
78540         }
78541     },
78542
78543     onDrag: function(e) {
78544         var xy = e.getXY(),
78545             me = this,
78546             sprite = me.sprite,
78547             attr = sprite.attr;
78548         me.translateX = xy[0] - me.prevX;
78549         me.translateY = xy[1] - me.prevY;
78550         sprite.setAttributes({
78551             translate: {
78552                 x: me.translateX,
78553                 y: me.translateY
78554             }
78555         }, true);
78556         if (sprite.vml) {
78557             me.prevX = xy[0] + attr.x || 0;
78558             me.prevY = xy[1] + attr.y || 0;
78559         }
78560     }
78561 });
78562 /**
78563  * @class Ext.draw.Sprite
78564  * @extends Object
78565  *
78566  * A Sprite is an object rendered in a Drawing surface. There are different options and types of sprites.
78567  * The configuration of a Sprite is an object with the following properties:
78568  *
78569  * - **type** - (String) The type of the sprite. Possible options are 'circle', 'path', 'rect', 'text', 'square', 'image'. 
78570  * - **width** - (Number) Used in rectangle sprites, the width of the rectangle.
78571  * - **height** - (Number) Used in rectangle sprites, the height of the rectangle.
78572  * - **size** - (Number) Used in square sprites, the dimension of the square.
78573  * - **radius** - (Number) Used in circle sprites, the radius of the circle.
78574  * - **x** - (Number) The position along the x-axis.
78575  * - **y** - (Number) The position along the y-axis.
78576  * - **path** - (Array) Used in path sprites, the path of the sprite written in SVG-like path syntax.
78577  * - **opacity** - (Number) The opacity of the sprite.
78578  * - **fill** - (String) The fill color.
78579  * - **stroke** - (String) The stroke color.
78580  * - **stroke-width** - (Number) The width of the stroke.
78581  * - **font** - (String) Used with text type sprites. The full font description. Uses the same syntax as the CSS `font` parameter.
78582  * - **text** - (String) Used with text type sprites. The text itself.
78583  * 
78584  * Additionally there are three transform objects that can be set with `setAttributes` which are `translate`, `rotate` and
78585  * `scale`.
78586  * 
78587  * For translate, the configuration object contains x and y attributes that indicate where to
78588  * translate the object. For example:
78589  * 
78590  *     sprite.setAttributes({
78591  *       translate: {
78592  *        x: 10,
78593  *        y: 10
78594  *       }
78595  *     }, true);
78596  * 
78597  * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
78598  * and a `degrees` attribute that specifies the rotation in degrees. For example:
78599  * 
78600  *     sprite.setAttributes({
78601  *       rotate: {
78602  *        degrees: 90
78603  *       }
78604  *     }, true);
78605  * 
78606  * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
78607  * 
78608  *     sprite.setAttributes({
78609  *       scale: {
78610  *        x: 10,
78611  *        y: 3
78612  *       }
78613  *     }, true);
78614  *
78615  * Sprites can be created with a reference to a {@link Ext.draw.Surface}
78616  *
78617  *      var drawComponent = Ext.create('Ext.draw.Component', options here...);
78618  *
78619  *      var sprite = Ext.create('Ext.draw.Sprite', {
78620  *          type: 'circle',
78621  *          fill: '#ff0',
78622  *          surface: drawComponent.surface,
78623  *          radius: 5
78624  *      });
78625  *
78626  * Sprites can also be added to the surface as a configuration object:
78627  *
78628  *      var sprite = drawComponent.surface.add({
78629  *          type: 'circle',
78630  *          fill: '#ff0',
78631  *          radius: 5
78632  *      });
78633  *
78634  * In order to properly apply properties and render the sprite we have to
78635  * `show` the sprite setting the option `redraw` to `true`:
78636  *
78637  *      sprite.show(true);
78638  *
78639  * The constructor configuration object of the Sprite can also be used and passed into the {@link Ext.draw.Surface}
78640  * add method to append a new sprite to the canvas. For example:
78641  *
78642  *     drawComponent.surface.add({
78643  *         type: 'circle',
78644  *         fill: '#ffc',
78645  *         radius: 100,
78646  *         x: 100,
78647  *         y: 100
78648  *     });
78649  */
78650 Ext.define('Ext.draw.Sprite', {
78651     /* Begin Definitions */
78652
78653     mixins: {
78654         observable: 'Ext.util.Observable',
78655         animate: 'Ext.util.Animate'
78656     },
78657
78658     requires: ['Ext.draw.SpriteDD'],
78659
78660     /* End Definitions */
78661
78662     dirty: false,
78663     dirtyHidden: false,
78664     dirtyTransform: false,
78665     dirtyPath: true,
78666     dirtyFont: true,
78667     zIndexDirty: true,
78668     isSprite: true,
78669     zIndex: 0,
78670     fontProperties: [
78671         'font',
78672         'font-size',
78673         'font-weight',
78674         'font-style',
78675         'font-family',
78676         'text-anchor',
78677         'text'
78678     ],
78679     pathProperties: [
78680         'x',
78681         'y',
78682         'd',
78683         'path',
78684         'height',
78685         'width',
78686         'radius',
78687         'r',
78688         'rx',
78689         'ry',
78690         'cx',
78691         'cy'
78692     ],
78693     constructor: function(config) {
78694         var me = this;
78695         config = config || {};
78696         me.id = Ext.id(null, 'ext-sprite-');
78697         me.transformations = [];
78698         Ext.copyTo(this, config, 'surface,group,type,draggable');
78699         //attribute bucket
78700         me.bbox = {};
78701         me.attr = {
78702             zIndex: 0,
78703             translation: {
78704                 x: null,
78705                 y: null
78706             },
78707             rotation: {
78708                 degrees: null,
78709                 x: null,
78710                 y: null
78711             },
78712             scaling: {
78713                 x: null,
78714                 y: null,
78715                 cx: null,
78716                 cy: null
78717             }
78718         };
78719         //delete not bucket attributes
78720         delete config.surface;
78721         delete config.group;
78722         delete config.type;
78723         delete config.draggable;
78724         me.setAttributes(config);
78725         me.addEvents(
78726             'beforedestroy',
78727             'destroy',
78728             'render',
78729             'mousedown',
78730             'mouseup',
78731             'mouseover',
78732             'mouseout',
78733             'mousemove',
78734             'click'
78735         );
78736         me.mixins.observable.constructor.apply(this, arguments);
78737     },
78738
78739     /**
78740      * <p>If this Sprite is configured {@link #draggable}, this property will contain
78741      * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.</p>
78742      * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
78743      * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
78744      * @type Ext.dd.DragSource.
78745      * @property dd
78746      */
78747     initDraggable: function() {
78748         var me = this;
78749         me.draggable = true;
78750         //create element if it doesn't exist.
78751         if (!me.el) {
78752             me.surface.createSpriteElement(me);
78753         }
78754         me.dd = Ext.create('Ext.draw.SpriteDD', me, Ext.isBoolean(me.draggable) ? null : me.draggable);
78755         me.on('beforedestroy', me.dd.destroy, me.dd);
78756     },
78757
78758     /**
78759      * Change the attributes of the sprite.
78760      * @param {Object} attrs attributes to be changed on the sprite.
78761      * @param {Boolean} redraw Flag to immediatly draw the change.
78762      * @return {Ext.draw.Sprite} this
78763      */
78764     setAttributes: function(attrs, redraw) {
78765         var me = this,
78766             fontProps = me.fontProperties,
78767             fontPropsLength = fontProps.length,
78768             pathProps = me.pathProperties,
78769             pathPropsLength = pathProps.length,
78770             hasSurface = !!me.surface,
78771             custom = hasSurface && me.surface.customAttributes || {},
78772             spriteAttrs = me.attr,
78773             attr, i, translate, translation, rotate, rotation, scale, scaling;
78774
78775         attrs = Ext.apply({}, attrs);
78776         for (attr in custom) {
78777             if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
78778                 Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
78779             }
78780         }
78781
78782         // Flag a change in hidden
78783         if (!!attrs.hidden !== !!spriteAttrs.hidden) {
78784             me.dirtyHidden = true;
78785         }
78786
78787         // Flag path change
78788         for (i = 0; i < pathPropsLength; i++) {
78789             attr = pathProps[i];
78790             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
78791                 me.dirtyPath = true;
78792                 break;
78793             }
78794         }
78795
78796         // Flag zIndex change
78797         if ('zIndex' in attrs) {
78798             me.zIndexDirty = true;
78799         }
78800
78801         // Flag font/text change
78802         for (i = 0; i < fontPropsLength; i++) {
78803             attr = fontProps[i];
78804             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
78805                 me.dirtyFont = true;
78806                 break;
78807             }
78808         }
78809
78810         translate = attrs.translate;
78811         translation = spriteAttrs.translation;
78812         if (translate) {
78813             if ((translate.x && translate.x !== translation.x) ||
78814                 (translate.y && translate.y !== translation.y)) {
78815                 Ext.apply(translation, translate);
78816                 me.dirtyTransform = true;
78817             }
78818             delete attrs.translate;
78819         }
78820
78821         rotate = attrs.rotate;
78822         rotation = spriteAttrs.rotation;
78823         if (rotate) {
78824             if ((rotate.x && rotate.x !== rotation.x) || 
78825                 (rotate.y && rotate.y !== rotation.y) ||
78826                 (rotate.degrees && rotate.degrees !== rotation.degrees)) {
78827                 Ext.apply(rotation, rotate);
78828                 me.dirtyTransform = true;
78829             }
78830             delete attrs.rotate;
78831         }
78832
78833         scale = attrs.scale;
78834         scaling = spriteAttrs.scaling;
78835         if (scale) {
78836             if ((scale.x && scale.x !== scaling.x) || 
78837                 (scale.y && scale.y !== scaling.y) ||
78838                 (scale.cx && scale.cx !== scaling.cx) ||
78839                 (scale.cy && scale.cy !== scaling.cy)) {
78840                 Ext.apply(scaling, scale);
78841                 me.dirtyTransform = true;
78842             }
78843             delete attrs.scale;
78844         }
78845
78846         Ext.apply(spriteAttrs, attrs);
78847         me.dirty = true;
78848
78849         if (redraw === true && hasSurface) {
78850             me.redraw();
78851         }
78852         return this;
78853     },
78854
78855     /**
78856      * Retrieve the bounding box of the sprite. This will be returned as an object with x, y, width, and height properties.
78857      * @return {Object} bbox
78858      */
78859     getBBox: function() {
78860         return this.surface.getBBox(this);
78861     },
78862     
78863     setText: function(text) {
78864         return this.surface.setText(this, text);
78865     },
78866
78867     /**
78868      * Hide the sprite.
78869      * @param {Boolean} redraw Flag to immediatly draw the change.
78870      * @return {Ext.draw.Sprite} this
78871      */
78872     hide: function(redraw) {
78873         this.setAttributes({
78874             hidden: true
78875         }, redraw);
78876         return this;
78877     },
78878
78879     /**
78880      * Show the sprite.
78881      * @param {Boolean} redraw Flag to immediatly draw the change.
78882      * @return {Ext.draw.Sprite} this
78883      */
78884     show: function(redraw) {
78885         this.setAttributes({
78886             hidden: false
78887         }, redraw);
78888         return this;
78889     },
78890
78891     /**
78892      * Remove the sprite.
78893      */
78894     remove: function() {
78895         if (this.surface) {
78896             this.surface.remove(this);
78897             return true;
78898         }
78899         return false;
78900     },
78901
78902     onRemove: function() {
78903         this.surface.onRemove(this);
78904     },
78905
78906     /**
78907      * Removes the sprite and clears all listeners.
78908      */
78909     destroy: function() {
78910         var me = this;
78911         if (me.fireEvent('beforedestroy', me) !== false) {
78912             me.remove();
78913             me.surface.onDestroy(me);
78914             me.clearListeners();
78915             me.fireEvent('destroy');
78916         }
78917     },
78918
78919     /**
78920      * Redraw the sprite.
78921      * @return {Ext.draw.Sprite} this
78922      */
78923     redraw: function() {
78924         this.surface.renderItem(this);
78925         return this;
78926     },
78927
78928     /**
78929      * Wrapper for setting style properties, also takes single object parameter of multiple styles.
78930      * @param {String/Object} property The style property to be set, or an object of multiple styles.
78931      * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
78932      * @return {Ext.draw.Sprite} this
78933      */
78934     setStyle: function() {
78935         this.el.setStyle.apply(this.el, arguments);
78936         return this;
78937     },
78938
78939     /**
78940      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.  Note this method
78941      * is severly limited in VML.
78942      * @param {String/Array} className The CSS class to add, or an array of classes
78943      * @return {Ext.draw.Sprite} this
78944      */
78945     addCls: function(obj) {
78946         this.surface.addCls(this, obj);
78947         return this;
78948     },
78949
78950     /**
78951      * Removes one or more CSS classes from the element.
78952      * @param {String/Array} className The CSS class to remove, or an array of classes.  Note this method
78953      * is severly limited in VML.
78954      * @return {Ext.draw.Sprite} this
78955      */
78956     removeCls: function(obj) {
78957         this.surface.removeCls(this, obj);
78958         return this;
78959     }
78960 });
78961
78962 /**
78963  * @class Ext.draw.engine.Svg
78964  * @extends Ext.draw.Surface
78965  * Provides specific methods to draw with SVG.
78966  */
78967 Ext.define('Ext.draw.engine.Svg', {
78968
78969     /* Begin Definitions */
78970
78971     extend: 'Ext.draw.Surface',
78972
78973     requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.core.Element'],
78974
78975     /* End Definitions */
78976
78977     engine: 'Svg',
78978
78979     trimRe: /^\s+|\s+$/g,
78980     spacesRe: /\s+/,
78981     xlink: "http:/" + "/www.w3.org/1999/xlink",
78982
78983     translateAttrs: {
78984         radius: "r",
78985         radiusX: "rx",
78986         radiusY: "ry",
78987         path: "d",
78988         lineWidth: "stroke-width",
78989         fillOpacity: "fill-opacity",
78990         strokeOpacity: "stroke-opacity",
78991         strokeLinejoin: "stroke-linejoin"
78992     },
78993
78994     minDefaults: {
78995         circle: {
78996             cx: 0,
78997             cy: 0,
78998             r: 0,
78999             fill: "none",
79000             stroke: null,
79001             "stroke-width": null,
79002             opacity: null,
79003             "fill-opacity": null,
79004             "stroke-opacity": null
79005         },
79006         ellipse: {
79007             cx: 0,
79008             cy: 0,
79009             rx: 0,
79010             ry: 0,
79011             fill: "none",
79012             stroke: null,
79013             "stroke-width": null,
79014             opacity: null,
79015             "fill-opacity": null,
79016             "stroke-opacity": null
79017         },
79018         rect: {
79019             x: 0,
79020             y: 0,
79021             width: 0,
79022             height: 0,
79023             rx: 0,
79024             ry: 0,
79025             fill: "none",
79026             stroke: null,
79027             "stroke-width": null,
79028             opacity: null,
79029             "fill-opacity": null,
79030             "stroke-opacity": null
79031         },
79032         text: {
79033             x: 0,
79034             y: 0,
79035             "text-anchor": "start",
79036             "font-family": null,
79037             "font-size": null,
79038             "font-weight": null,
79039             "font-style": null,
79040             fill: "#000",
79041             stroke: null,
79042             "stroke-width": null,
79043             opacity: null,
79044             "fill-opacity": null,
79045             "stroke-opacity": null
79046         },
79047         path: {
79048             d: "M0,0",
79049             fill: "none",
79050             stroke: null,
79051             "stroke-width": null,
79052             opacity: null,
79053             "fill-opacity": null,
79054             "stroke-opacity": null
79055         },
79056         image: {
79057             x: 0,
79058             y: 0,
79059             width: 0,
79060             height: 0,
79061             preserveAspectRatio: "none",
79062             opacity: null
79063         }
79064     },
79065
79066     createSvgElement: function(type, attrs) {
79067         var el = this.domRef.createElementNS("http:/" + "/www.w3.org/2000/svg", type),
79068             key;
79069         if (attrs) {
79070             for (key in attrs) {
79071                 el.setAttribute(key, String(attrs[key]));
79072             }
79073         }
79074         return el;
79075     },
79076
79077     createSpriteElement: function(sprite) {
79078         // Create svg element and append to the DOM.
79079         var el = this.createSvgElement(sprite.type);
79080         el.id = sprite.id;
79081         if (el.style) {
79082             el.style.webkitTapHighlightColor = "rgba(0,0,0,0)";
79083         }
79084         sprite.el = Ext.get(el);
79085         this.applyZIndex(sprite); //performs the insertion
79086         sprite.matrix = Ext.create('Ext.draw.Matrix');
79087         sprite.bbox = {
79088             plain: 0,
79089             transform: 0
79090         };
79091         sprite.fireEvent("render", sprite);
79092         return el;
79093     },
79094
79095     getBBox: function (sprite, isWithoutTransform) {
79096         var realPath = this["getPath" + sprite.type](sprite);
79097         if (isWithoutTransform) {
79098             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
79099             return sprite.bbox.plain;
79100         }
79101         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
79102         return sprite.bbox.transform;
79103     },
79104     
79105     getBBoxText: function (sprite) {
79106         var bbox = {},
79107             bb, height, width, i, ln, el;
79108
79109         if (sprite && sprite.el) {
79110             el = sprite.el.dom;
79111             try {
79112                 bbox = el.getBBox();
79113                 return bbox;
79114             } catch(e) {
79115                 // Firefox 3.0.x plays badly here
79116             }
79117             bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
79118             ln = el.getNumberOfChars();
79119             for (i = 0; i < ln; i++) {
79120                 bb = el.getExtentOfChar(i);
79121                 bbox.y = Math.min(bb.y, bbox.y);
79122                 height = bb.y + bb.height - bbox.y;
79123                 bbox.height = Math.max(bbox.height, height);
79124                 width = bb.x + bb.width - bbox.x;
79125                 bbox.width = Math.max(bbox.width, width);
79126             }
79127             return bbox;
79128         }
79129     },
79130
79131     hide: function() {
79132         Ext.get(this.el).hide();
79133     },
79134
79135     show: function() {
79136         Ext.get(this.el).show();
79137     },
79138
79139     hidePrim: function(sprite) {
79140         this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
79141     },
79142
79143     showPrim: function(sprite) {
79144         this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
79145     },
79146
79147     getDefs: function() {
79148         return this._defs || (this._defs = this.createSvgElement("defs"));
79149     },
79150
79151     transform: function(sprite) {
79152         var me = this,
79153             matrix = Ext.create('Ext.draw.Matrix'),
79154             transforms = sprite.transformations,
79155             transformsLength = transforms.length,
79156             i = 0,
79157             transform, type;
79158             
79159         for (; i < transformsLength; i++) {
79160             transform = transforms[i];
79161             type = transform.type;
79162             if (type == "translate") {
79163                 matrix.translate(transform.x, transform.y);
79164             }
79165             else if (type == "rotate") {
79166                 matrix.rotate(transform.degrees, transform.x, transform.y);
79167             }
79168             else if (type == "scale") {
79169                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
79170             }
79171         }
79172         sprite.matrix = matrix;
79173         sprite.el.set({transform: matrix.toSvg()});
79174     },
79175
79176     setSize: function(w, h) {
79177         var me = this,
79178             el = me.el;
79179         
79180         w = +w || me.width;
79181         h = +h || me.height;
79182         me.width = w;
79183         me.height = h;
79184
79185         el.setSize(w, h);
79186         el.set({
79187             width: w,
79188             height: h
79189         });
79190         me.callParent([w, h]);
79191     },
79192
79193     /**
79194      * Get the region for the surface's canvas area
79195      * @returns {Ext.util.Region}
79196      */
79197     getRegion: function() {
79198         // Mozilla requires using the background rect because the svg element returns an
79199         // incorrect region. Webkit gives no region for the rect and must use the svg element.
79200         var svgXY = this.el.getXY(),
79201             rectXY = this.bgRect.getXY(),
79202             max = Math.max,
79203             x = max(svgXY[0], rectXY[0]),
79204             y = max(svgXY[1], rectXY[1]);
79205         return {
79206             left: x,
79207             top: y,
79208             right: x + this.width,
79209             bottom: y + this.height
79210         };
79211     },
79212
79213     onRemove: function(sprite) {
79214         if (sprite.el) {
79215             sprite.el.remove();
79216             delete sprite.el;
79217         }
79218         this.callParent(arguments);
79219     },
79220     
79221     setViewBox: function(x, y, width, height) {
79222         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
79223             this.callParent(arguments);
79224             this.el.dom.setAttribute("viewBox", [x, y, width, height].join(" "));
79225         }
79226     },
79227
79228     render: function (container) {
79229         var me = this;
79230         if (!me.el) {
79231             var width = me.width || 10,
79232                 height = me.height || 10,
79233                 el = me.createSvgElement('svg', {
79234                     xmlns: "http:/" + "/www.w3.org/2000/svg",
79235                     version: 1.1,
79236                     width: width,
79237                     height: height
79238                 }),
79239                 defs = me.getDefs(),
79240
79241                 // Create a rect that is always the same size as the svg root; this serves 2 purposes:
79242                 // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
79243                 // use it rather than the svg element for retrieving the correct client rect of the
79244                 // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
79245                 bgRect = me.createSvgElement("rect", {
79246                     width: "100%",
79247                     height: "100%",
79248                     fill: "#000",
79249                     stroke: "none",
79250                     opacity: 0
79251                 }),
79252                 webkitRect;
79253             
79254                 if (Ext.isSafari3) {
79255                     // Rect that we will show/hide to fix old WebKit bug with rendering issues.
79256                     webkitRect = me.createSvgElement("rect", {
79257                         x: -10,
79258                         y: -10,
79259                         width: "110%",
79260                         height: "110%",
79261                         fill: "none",
79262                         stroke: "#000"
79263                     });
79264                 }
79265             el.appendChild(defs);
79266             if (Ext.isSafari3) {
79267                 el.appendChild(webkitRect);
79268             }
79269             el.appendChild(bgRect);
79270             container.appendChild(el);
79271             me.el = Ext.get(el);
79272             me.bgRect = Ext.get(bgRect);
79273             if (Ext.isSafari3) {
79274                 me.webkitRect = Ext.get(webkitRect);
79275                 me.webkitRect.hide();
79276             }
79277             me.el.on({
79278                 scope: me,
79279                 mouseup: me.onMouseUp,
79280                 mousedown: me.onMouseDown,
79281                 mouseover: me.onMouseOver,
79282                 mouseout: me.onMouseOut,
79283                 mousemove: me.onMouseMove,
79284                 mouseenter: me.onMouseEnter,
79285                 mouseleave: me.onMouseLeave,
79286                 click: me.onClick
79287             });
79288         }
79289         me.renderAll();
79290     },
79291
79292     // private
79293     onMouseEnter: function(e) {
79294         if (this.el.parent().getRegion().contains(e.getPoint())) {
79295             this.fireEvent('mouseenter', e);
79296         }
79297     },
79298
79299     // private
79300     onMouseLeave: function(e) {
79301         if (!this.el.parent().getRegion().contains(e.getPoint())) {
79302             this.fireEvent('mouseleave', e);
79303         }
79304     },
79305     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
79306     processEvent: function(name, e) {
79307         var target = e.getTarget(),
79308             surface = this.surface,
79309             sprite;
79310
79311         this.fireEvent(name, e);
79312         // We wrap text types in a tspan, sprite is the parent.
79313         if (target.nodeName == "tspan" && target.parentNode) {
79314             target = target.parentNode;
79315         }
79316         sprite = this.items.get(target.id);
79317         if (sprite) {
79318             sprite.fireEvent(name, sprite, e);
79319         }
79320     },
79321
79322     /* @private - Wrap SVG text inside a tspan to allow for line wrapping.  In addition this normallizes
79323      * the baseline for text the vertical middle of the text to be the same as VML.
79324      */
79325     tuneText: function (sprite, attrs) {
79326         var el = sprite.el.dom,
79327             tspans = [],
79328             height, tspan, text, i, ln, texts, factor;
79329
79330         if (attrs.hasOwnProperty("text")) {
79331            tspans = this.setText(sprite, attrs.text);
79332         }
79333         // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
79334         if (tspans.length) {
79335             height = this.getBBoxText(sprite).height;
79336             for (i = 0, ln = tspans.length; i < ln; i++) {
79337                 // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
79338                 // so we are going to normalize that here
79339                 factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
79340                 tspans[i].setAttribute("dy", i ? height * 1.2 : height / factor);
79341             }
79342             sprite.dirty = true;
79343         }
79344     },
79345
79346     setText: function(sprite, textString) {
79347          var me = this,
79348              el = sprite.el.dom,
79349              x = el.getAttribute("x"),
79350              tspans = [],
79351              height, tspan, text, i, ln, texts;
79352         
79353         while (el.firstChild) {
79354             el.removeChild(el.firstChild);
79355         }
79356         // Wrap each row into tspan to emulate rows
79357         texts = String(textString).split("\n");
79358         for (i = 0, ln = texts.length; i < ln; i++) {
79359             text = texts[i];
79360             if (text) {
79361                 tspan = me.createSvgElement("tspan");
79362                 tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
79363                 tspan.setAttribute("x", x);
79364                 el.appendChild(tspan);
79365                 tspans[i] = tspan;
79366             }
79367         }
79368         return tspans;
79369     },
79370
79371     renderAll: function() {
79372         this.items.each(this.renderItem, this);
79373     },
79374
79375     renderItem: function (sprite) {
79376         if (!this.el) {
79377             return;
79378         }
79379         if (!sprite.el) {
79380             this.createSpriteElement(sprite);
79381         }
79382         if (sprite.zIndexDirty) {
79383             this.applyZIndex(sprite);
79384         }
79385         if (sprite.dirty) {
79386             this.applyAttrs(sprite);
79387             this.applyTransformations(sprite);
79388         }
79389     },
79390
79391     redraw: function(sprite) {
79392         sprite.dirty = sprite.zIndexDirty = true;
79393         this.renderItem(sprite);
79394     },
79395
79396     applyAttrs: function (sprite) {
79397         var me = this,
79398             el = sprite.el,
79399             group = sprite.group,
79400             sattr = sprite.attr,
79401             groups, i, ln, attrs, font, key, style, name, rect;
79402
79403         if (group) {
79404             groups = [].concat(group);
79405             ln = groups.length;
79406             for (i = 0; i < ln; i++) {
79407                 group = groups[i];
79408                 me.getGroup(group).add(sprite);
79409             }
79410             delete sprite.group;
79411         }
79412         attrs = me.scrubAttrs(sprite) || {};
79413
79414         // if (sprite.dirtyPath) {
79415             sprite.bbox.plain = 0;
79416             sprite.bbox.transform = 0;
79417             if (sprite.type == "circle" || sprite.type == "ellipse") {
79418                 attrs.cx = attrs.cx || attrs.x;
79419                 attrs.cy = attrs.cy || attrs.y;
79420             }
79421             else if (sprite.type == "rect") {
79422                 attrs.rx = attrs.ry = attrs.r;
79423             }
79424             else if (sprite.type == "path" && attrs.d) {
79425                 attrs.d = Ext.draw.Draw.pathToString(Ext.draw.Draw.pathToAbsolute(attrs.d));
79426                 
79427             }
79428             sprite.dirtyPath = false;
79429         // }
79430         // else {
79431         //     delete attrs.d;
79432         // }
79433
79434         if (attrs['clip-rect']) {
79435             me.setClip(sprite, attrs);
79436             delete attrs['clip-rect'];
79437         }
79438         if (sprite.type == 'text' && attrs.font && sprite.dirtyFont) {
79439             el.set({ style: "font: " + attrs.font});
79440             sprite.dirtyFont = false;
79441         }
79442         if (sprite.type == "image") {
79443             el.dom.setAttributeNS(me.xlink, "href", attrs.src);
79444         }
79445         Ext.applyIf(attrs, me.minDefaults[sprite.type]);
79446
79447         if (sprite.dirtyHidden) {
79448             (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
79449             sprite.dirtyHidden = false;
79450         }
79451         for (key in attrs) {
79452             if (attrs.hasOwnProperty(key) && attrs[key] != null) {
79453                 el.dom.setAttribute(key, attrs[key]);
79454             }
79455         }
79456         if (sprite.type == 'text') {
79457             me.tuneText(sprite, attrs);
79458         }
79459
79460         //set styles
79461         style = sattr.style;
79462         if (style) {
79463             el.setStyle(style);
79464         }
79465
79466         sprite.dirty = false;
79467
79468         if (Ext.isSafari3) {
79469             // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
79470             me.webkitRect.show();
79471             setTimeout(function () {
79472                 me.webkitRect.hide();
79473             });
79474         }
79475     },
79476
79477     setClip: function(sprite, params) {
79478         var me = this,
79479             rect = params["clip-rect"],
79480             clipEl, clipPath;
79481         if (rect) {
79482             if (sprite.clip) {
79483                 sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
79484             }
79485             clipEl = me.createSvgElement('clipPath');
79486             clipPath = me.createSvgElement('rect');
79487             clipEl.id = Ext.id(null, 'ext-clip-');
79488             clipPath.setAttribute("x", rect.x);
79489             clipPath.setAttribute("y", rect.y);
79490             clipPath.setAttribute("width", rect.width);
79491             clipPath.setAttribute("height", rect.height);
79492             clipEl.appendChild(clipPath);
79493             me.getDefs().appendChild(clipEl);
79494             sprite.el.dom.setAttribute("clip-path", "url(#" + clipEl.id + ")");
79495             sprite.clip = clipPath;
79496         }
79497         // if (!attrs[key]) {
79498         //     var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, ""));
79499         //     clip && clip.parentNode.removeChild(clip);
79500         //     sprite.el.setAttribute("clip-path", "");
79501         //     delete attrss.clip;
79502         // }
79503     },
79504
79505     /**
79506      * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
79507      * @param {Ext.draw.Sprite} sprite
79508      */
79509     applyZIndex: function(sprite) {
79510         var idx = this.normalizeSpriteCollection(sprite),
79511             el = sprite.el,
79512             prevEl;
79513         if (this.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect 
79514             if (idx > 0) {
79515                 // Find the first previous sprite which has its DOM element created already
79516                 do {
79517                     prevEl = this.items.getAt(--idx).el;
79518                 } while (!prevEl && idx > 0);
79519             }
79520             el.insertAfter(prevEl || this.bgRect);
79521         }
79522         sprite.zIndexDirty = false;
79523     },
79524
79525     createItem: function (config) {
79526         var sprite = Ext.create('Ext.draw.Sprite', config);
79527         sprite.surface = this;
79528         return sprite;
79529     },
79530
79531     addGradient: function(gradient) {
79532         gradient = Ext.draw.Draw.parseGradient(gradient);
79533         var ln = gradient.stops.length,
79534             vector = gradient.vector,
79535             gradientEl,
79536             stop,
79537             stopEl,
79538             i;
79539         if (gradient.type == "linear") {
79540             gradientEl = this.createSvgElement("linearGradient");
79541             gradientEl.setAttribute("x1", vector[0]);
79542             gradientEl.setAttribute("y1", vector[1]);
79543             gradientEl.setAttribute("x2", vector[2]);
79544             gradientEl.setAttribute("y2", vector[3]);
79545         }
79546         else {
79547             gradientEl = this.createSvgElement("radialGradient");
79548             gradientEl.setAttribute("cx", gradient.centerX);
79549             gradientEl.setAttribute("cy", gradient.centerY);
79550             gradientEl.setAttribute("r", gradient.radius);
79551             if (Ext.isNumber(gradient.focalX) && Ext.isNumber(gradient.focalY)) {
79552                 gradientEl.setAttribute("fx", gradient.focalX);
79553                 gradientEl.setAttribute("fy", gradient.focalY);
79554             }
79555         }    
79556         gradientEl.id = gradient.id;
79557         this.getDefs().appendChild(gradientEl);
79558
79559         for (i = 0; i < ln; i++) {
79560             stop = gradient.stops[i];
79561             stopEl = this.createSvgElement("stop");
79562             stopEl.setAttribute("offset", stop.offset + "%");
79563             stopEl.setAttribute("stop-color", stop.color);
79564             stopEl.setAttribute("stop-opacity",stop.opacity);
79565             gradientEl.appendChild(stopEl);
79566         }
79567     },
79568
79569     /**
79570      * Checks if the specified CSS class exists on this element's DOM node.
79571      * @param {String} className The CSS class to check for
79572      * @return {Boolean} True if the class exists, else false
79573      */
79574     hasCls: function(sprite, className) {
79575         return className && (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
79576     },
79577
79578     addCls: function(sprite, className) {
79579         var el = sprite.el,
79580             i,
79581             len,
79582             v,
79583             cls = [],
79584             curCls =  el.getAttribute('class') || '';
79585         // Separate case is for speed
79586         if (!Ext.isArray(className)) {
79587             if (typeof className == 'string' && !this.hasCls(sprite, className)) {
79588                 el.set({ 'class': curCls + ' ' + className });
79589             }
79590         }
79591         else {
79592             for (i = 0, len = className.length; i < len; i++) {
79593                 v = className[i];
79594                 if (typeof v == 'string' && (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
79595                     cls.push(v);
79596                 }
79597             }
79598             if (cls.length) {
79599                 el.set({ 'class': ' ' + cls.join(' ') });
79600             }
79601         }
79602     },
79603
79604     removeCls: function(sprite, className) {
79605         var me = this,
79606             el = sprite.el,
79607             curCls =  el.getAttribute('class') || '',
79608             i, idx, len, cls, elClasses;
79609         if (!Ext.isArray(className)){
79610             className = [className];
79611         }
79612         if (curCls) {
79613             elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
79614             for (i = 0, len = className.length; i < len; i++) {
79615                 cls = className[i];
79616                 if (typeof cls == 'string') {
79617                     cls = cls.replace(me.trimRe, '');
79618                     idx = Ext.Array.indexOf(elClasses, cls);
79619                     if (idx != -1) {
79620                         elClasses.splice(idx, 1);
79621                     }
79622                 }
79623             }
79624             el.set({ 'class': elClasses.join(' ') });
79625         }
79626     },
79627
79628     destroy: function() {
79629         var me = this;
79630         
79631         me.callParent();
79632         if (me.el) {
79633             me.el.remove();
79634         }
79635         delete me.el;
79636     }
79637 });
79638 /**
79639  * @class Ext.draw.engine.Vml
79640  * @extends Ext.draw.Surface
79641  * Provides specific methods to draw with VML.
79642  */
79643
79644 Ext.define('Ext.draw.engine.Vml', {
79645
79646     /* Begin Definitions */
79647
79648     extend: 'Ext.draw.Surface',
79649
79650     requires: ['Ext.draw.Draw', 'Ext.draw.Color', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.core.Element'],
79651
79652     /* End Definitions */
79653
79654     engine: 'Vml',
79655
79656     map: {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
79657     bitesRe: /([clmz]),?([^clmz]*)/gi,
79658     valRe: /-?[^,\s-]+/g,
79659     fillUrlRe: /^url\(\s*['"]?([^\)]+?)['"]?\s*\)$/i,
79660     pathlike: /^(path|rect)$/,
79661     NonVmlPathRe: /[ahqstv]/ig, // Non-VML Pathing ops
79662     partialPathRe: /[clmz]/g,
79663     fontFamilyRe: /^['"]+|['"]+$/g,
79664     baseVmlCls: Ext.baseCSSPrefix + 'vml-base',
79665     vmlGroupCls: Ext.baseCSSPrefix + 'vml-group',
79666     spriteCls: Ext.baseCSSPrefix + 'vml-sprite',
79667     measureSpanCls: Ext.baseCSSPrefix + 'vml-measure-span',
79668     zoom: 21600,
79669     coordsize: 1000,
79670     coordorigin: '0 0',
79671
79672     // @private
79673     // Convert an SVG standard path into a VML path
79674     path2vml: function (path) {
79675         var me = this,
79676             nonVML =  me.NonVmlPathRe,
79677             map = me.map,
79678             val = me.valRe,
79679             zoom = me.zoom,
79680             bites = me.bitesRe,
79681             command = Ext.Function.bind(Ext.draw.Draw.pathToAbsolute, Ext.draw.Draw),
79682             res, pa, p, r, i, ii, j, jj;
79683         if (String(path).match(nonVML)) {
79684             command = Ext.Function.bind(Ext.draw.Draw.path2curve, Ext.draw.Draw);
79685         } else if (!String(path).match(me.partialPathRe)) {
79686             res = String(path).replace(bites, function (all, command, args) {
79687                 var vals = [],
79688                     isMove = command.toLowerCase() == "m",
79689                     res = map[command];
79690                 args.replace(val, function (value) {
79691                     if (isMove && vals[length] == 2) {
79692                         res += vals + map[command == "m" ? "l" : "L"];
79693                         vals = [];
79694                     }
79695                     vals.push(Math.round(value * zoom));
79696                 });
79697                 return res + vals;
79698             });
79699             return res;
79700         }
79701         pa = command(path);
79702         res = [];
79703         for (i = 0, ii = pa.length; i < ii; i++) {
79704             p = pa[i];
79705             r = pa[i][0].toLowerCase();
79706             if (r == "z") {
79707                 r = "x";
79708             }
79709             for (j = 1, jj = p.length; j < jj; j++) {
79710                 r += Math.round(p[j] * me.zoom) + (j != jj - 1 ? "," : "");
79711             }
79712             res.push(r);
79713         }
79714         return res.join(" ");
79715     },
79716
79717     // @private - set of attributes which need to be translated from the sprite API to the native browser API
79718     translateAttrs: {
79719         radius: "r",
79720         radiusX: "rx",
79721         radiusY: "ry",
79722         lineWidth: "stroke-width",
79723         fillOpacity: "fill-opacity",
79724         strokeOpacity: "stroke-opacity",
79725         strokeLinejoin: "stroke-linejoin"
79726     },
79727
79728     // @private - Minimun set of defaults for different types of sprites.
79729     minDefaults: {
79730         circle: {
79731             fill: "none",
79732             stroke: null,
79733             "stroke-width": null,
79734             opacity: null,
79735             "fill-opacity": null,
79736             "stroke-opacity": null
79737         },
79738         ellipse: {
79739             cx: 0,
79740             cy: 0,
79741             rx: 0,
79742             ry: 0,
79743             fill: "none",
79744             stroke: null,
79745             "stroke-width": null,
79746             opacity: null,
79747             "fill-opacity": null,
79748             "stroke-opacity": null
79749         },
79750         rect: {
79751             x: 0,
79752             y: 0,
79753             width: 0,
79754             height: 0,
79755             rx: 0,
79756             ry: 0,
79757             fill: "none",
79758             stroke: null,
79759             "stroke-width": null,
79760             opacity: null,
79761             "fill-opacity": null,
79762             "stroke-opacity": null
79763         },
79764         text: {
79765             x: 0,
79766             y: 0,
79767             "text-anchor": "start",
79768             font: '10px "Arial"',
79769             fill: "#000",
79770             stroke: null,
79771             "stroke-width": null,
79772             opacity: null,
79773             "fill-opacity": null,
79774             "stroke-opacity": null
79775         },
79776         path: {
79777             d: "M0,0",
79778             fill: "none",
79779             stroke: null,
79780             "stroke-width": null,
79781             opacity: null,
79782             "fill-opacity": null,
79783             "stroke-opacity": null
79784         },
79785         image: {
79786             x: 0,
79787             y: 0,
79788             width: 0,
79789             height: 0,
79790             preserveAspectRatio: "none",
79791             opacity: null
79792         }
79793     },
79794
79795     // private
79796     onMouseEnter: function(e) {
79797         this.fireEvent("mouseenter", e);
79798     },
79799
79800     // private
79801     onMouseLeave: function(e) {
79802         this.fireEvent("mouseleave", e);
79803     },
79804
79805     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
79806     processEvent: function(name, e) {
79807         var target = e.getTarget(),
79808             surface = this.surface,
79809             sprite;
79810         this.fireEvent(name, e);
79811         sprite = this.items.get(target.id);
79812         if (sprite) {
79813             sprite.fireEvent(name, sprite, e);
79814         }
79815     },
79816
79817     // Create the VML element/elements and append them to the DOM
79818     createSpriteElement: function(sprite) {
79819         var me = this,
79820             attr = sprite.attr,
79821             type = sprite.type,
79822             zoom = me.zoom,
79823             vml = sprite.vml || (sprite.vml = {}),
79824             round = Math.round,
79825             el = (type === 'image') ? me.createNode('image') : me.createNode('shape'),
79826             path, skew, textPath;
79827
79828         el.coordsize = zoom + ' ' + zoom;
79829         el.coordorigin = attr.coordorigin || "0 0";
79830         Ext.get(el).addCls(me.spriteCls);
79831         if (type == "text") {
79832             vml.path = path = me.createNode("path");
79833             path.textpathok = true;
79834             vml.textpath = textPath = me.createNode("textpath");
79835             textPath.on = true;
79836             el.appendChild(textPath);
79837             el.appendChild(path);
79838         }
79839         el.id = sprite.id;
79840         sprite.el = Ext.get(el);
79841         me.el.appendChild(el);
79842         if (type !== 'image') {
79843             skew = me.createNode("skew");
79844             skew.on = true;
79845             el.appendChild(skew);
79846             sprite.skew = skew;
79847         }
79848         sprite.matrix = Ext.create('Ext.draw.Matrix');
79849         sprite.bbox = {
79850             plain: null,
79851             transform: null
79852         };
79853         sprite.fireEvent("render", sprite);
79854         return sprite.el;
79855     },
79856
79857     // @private - Get bounding box for the sprite.  The Sprite itself has the public method.
79858     getBBox: function (sprite, isWithoutTransform) {
79859         var realPath = this["getPath" + sprite.type](sprite);
79860         if (isWithoutTransform) {
79861             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
79862             return sprite.bbox.plain;
79863         }
79864         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
79865         return sprite.bbox.transform;
79866     },
79867
79868     getBBoxText: function (sprite) {
79869         var vml = sprite.vml;
79870         return {
79871             x: vml.X + (vml.bbx || 0) - vml.W / 2,
79872             y: vml.Y - vml.H / 2,
79873             width: vml.W,
79874             height: vml.H
79875         };
79876     },
79877
79878     applyAttrs: function (sprite) {
79879         var me = this,
79880             vml = sprite.vml,
79881             group = sprite.group,
79882             spriteAttr = sprite.attr,
79883             el = sprite.el,
79884             dom = el.dom,
79885             style, name, groups, i, ln, scrubbedAttrs, font, key, bbox;
79886
79887         if (group) {
79888             groups = [].concat(group);
79889             ln = groups.length;
79890             for (i = 0; i < ln; i++) {
79891                 group = groups[i];
79892                 me.getGroup(group).add(sprite);
79893             }
79894             delete sprite.group;
79895         }
79896         scrubbedAttrs = me.scrubAttrs(sprite) || {};
79897
79898         if (sprite.zIndexDirty) {
79899             me.setZIndex(sprite);
79900         }
79901
79902         // Apply minimum default attributes
79903         Ext.applyIf(scrubbedAttrs, me.minDefaults[sprite.type]);
79904
79905         if (sprite.type == 'image') {
79906             Ext.apply(sprite.attr, {
79907                 x: scrubbedAttrs.x,
79908                 y: scrubbedAttrs.y,
79909                 width: scrubbedAttrs.width,
79910                 height: scrubbedAttrs.height
79911             });
79912             bbox = sprite.getBBox();
79913             el.setStyle({
79914                 width: bbox.width + 'px',
79915                 height: bbox.height + 'px'
79916             });
79917             dom.src = scrubbedAttrs.src;
79918         }
79919
79920         if (dom.href) {
79921             dom.href = scrubbedAttrs.href;
79922         }
79923         if (dom.title) {
79924             dom.title = scrubbedAttrs.title;
79925         }
79926         if (dom.target) {
79927             dom.target = scrubbedAttrs.target;
79928         }
79929         if (dom.cursor) {
79930             dom.cursor = scrubbedAttrs.cursor;
79931         }
79932
79933         // Change visibility
79934         if (sprite.dirtyHidden) {
79935             (scrubbedAttrs.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
79936             sprite.dirtyHidden = false;
79937         }
79938
79939         // Update path
79940         if (sprite.dirtyPath) {
79941             if (sprite.type == "circle" || sprite.type == "ellipse") {
79942                 var cx = scrubbedAttrs.x,
79943                     cy = scrubbedAttrs.y,
79944                     rx = scrubbedAttrs.rx || scrubbedAttrs.r || 0,
79945                     ry = scrubbedAttrs.ry || scrubbedAttrs.r || 0;
79946                 dom.path = Ext.String.format("ar{0},{1},{2},{3},{4},{1},{4},{1}",
79947                             Math.round((cx - rx) * me.zoom),
79948                             Math.round((cy - ry) * me.zoom),
79949                             Math.round((cx + rx) * me.zoom),
79950                             Math.round((cy + ry) * me.zoom),
79951                             Math.round(cx * me.zoom));
79952                 sprite.dirtyPath = false;
79953             }
79954             else if (sprite.type !== "text" && sprite.type !== 'image') {
79955                 sprite.attr.path = scrubbedAttrs.path = me.setPaths(sprite, scrubbedAttrs) || scrubbedAttrs.path;
79956                 dom.path = me.path2vml(scrubbedAttrs.path);
79957                 sprite.dirtyPath = false;
79958             }
79959         }
79960
79961         // Apply clipping
79962         if ("clip-rect" in scrubbedAttrs) {
79963             me.setClip(sprite, scrubbedAttrs);
79964         }
79965
79966         // Handle text (special handling required)
79967         if (sprite.type == "text") {
79968             me.setTextAttributes(sprite, scrubbedAttrs);
79969         }
79970
79971         // Handle fill and opacity
79972         if (scrubbedAttrs.opacity  || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
79973             me.setFill(sprite, scrubbedAttrs);
79974         }
79975
79976         // Handle stroke (all fills require a stroke element)
79977         if (scrubbedAttrs.stroke || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
79978             me.setStroke(sprite, scrubbedAttrs);
79979         }
79980         
79981         //set styles
79982         style = spriteAttr.style;
79983         if (style) {
79984             el.setStyle(style);
79985         }
79986
79987         sprite.dirty = false;
79988     },
79989
79990     setZIndex: function(sprite) {
79991         if (sprite.el) {
79992             if (sprite.attr.zIndex != undefined) {
79993                 sprite.el.setStyle('zIndex', sprite.attr.zIndex);
79994             }
79995             sprite.zIndexDirty = false;
79996         }
79997     },
79998
79999     // Normalize all virtualized types into paths.
80000     setPaths: function(sprite, params) {
80001         var spriteAttr = sprite.attr;
80002         // Clear bbox cache
80003         sprite.bbox.plain = null;
80004         sprite.bbox.transform = null;
80005         if (sprite.type == 'circle') {
80006             spriteAttr.rx = spriteAttr.ry = params.r;
80007             return Ext.draw.Draw.ellipsePath(sprite);
80008         }
80009         else if (sprite.type == 'ellipse') {
80010             spriteAttr.rx = params.rx;
80011             spriteAttr.ry = params.ry;
80012             return Ext.draw.Draw.ellipsePath(sprite);
80013         }
80014         else if (sprite.type == 'rect') {
80015             spriteAttr.rx = spriteAttr.ry = params.r;
80016             return Ext.draw.Draw.rectPath(sprite);
80017         }
80018         else if (sprite.type == 'path' && spriteAttr.path) {
80019             return Ext.draw.Draw.pathToAbsolute(spriteAttr.path);
80020         }
80021         return false;
80022     },
80023
80024     setFill: function(sprite, params) {
80025         var me = this,
80026             el = sprite.el.dom,
80027             fillEl = el.fill,
80028             newfill = false,
80029             opacity, gradient, fillUrl, rotation, angle;
80030
80031         if (!fillEl) {
80032             // NOT an expando (but it sure looks like one)...
80033             fillEl = el.fill = me.createNode("fill");
80034             newfill = true;
80035         }
80036         if (Ext.isArray(params.fill)) {
80037             params.fill = params.fill[0];
80038         }
80039         if (params.fill == "none") {
80040             fillEl.on = false;
80041         }
80042         else {
80043             if (typeof params.opacity == "number") {
80044                 fillEl.opacity = params.opacity;
80045             }
80046             if (typeof params["fill-opacity"] == "number") {
80047                 fillEl.opacity = params["fill-opacity"];
80048             }
80049             fillEl.on = true;
80050             if (typeof params.fill == "string") {
80051                 fillUrl = params.fill.match(me.fillUrlRe);
80052                 if (fillUrl) {
80053                     fillUrl = fillUrl[1];
80054                     // If the URL matches one of the registered gradients, render that gradient
80055                     if (fillUrl.charAt(0) == "#") {
80056                         gradient = me.gradientsColl.getByKey(fillUrl.substring(1));
80057                     }
80058                     if (gradient) {
80059                         // VML angle is offset and inverted from standard, and must be adjusted to match rotation transform
80060                         rotation = params.rotation;
80061                         angle = -(gradient.angle + 270 + (rotation ? rotation.degrees : 0)) % 360;
80062                         // IE will flip the angle at 0 degrees...
80063                         if (angle === 0) {
80064                             angle = 180;
80065                         }
80066                         fillEl.angle = angle;
80067                         fillEl.type = "gradient";
80068                         fillEl.method = "sigma";
80069                         fillEl.colors.value = gradient.colors;
80070                     }
80071                     // Otherwise treat it as an image
80072                     else {
80073                         fillEl.src = fillUrl;
80074                         fillEl.type = "tile";
80075                     }
80076                 }
80077                 else {
80078                     fillEl.color = Ext.draw.Color.toHex(params.fill);
80079                     fillEl.src = "";
80080                     fillEl.type = "solid";
80081                 }
80082             }
80083         }
80084         if (newfill) {
80085             el.appendChild(fillEl);
80086         }
80087     },
80088
80089     setStroke: function(sprite, params) {
80090         var me = this,
80091             el = sprite.el.dom,
80092             strokeEl = sprite.strokeEl,
80093             newStroke = false,
80094             width, opacity;
80095
80096         if (!strokeEl) {
80097             strokeEl = sprite.strokeEl = me.createNode("stroke");
80098             newStroke = true;
80099         }
80100         if (Ext.isArray(params.stroke)) {
80101             params.stroke = params.stroke[0];
80102         }
80103         if (!params.stroke || params.stroke == "none" || params.stroke == 0 || params["stroke-width"] == 0) {
80104             strokeEl.on = false;
80105         }
80106         else {
80107             strokeEl.on = true;
80108             if (params.stroke && !params.stroke.match(me.fillUrlRe)) {
80109                 // VML does NOT support a gradient stroke :(
80110                 strokeEl.color = Ext.draw.Color.toHex(params.stroke);
80111             }
80112             strokeEl.joinstyle = params["stroke-linejoin"];
80113             strokeEl.endcap = params["stroke-linecap"] || "round";
80114             strokeEl.miterlimit = params["stroke-miterlimit"] || 8;
80115             width = parseFloat(params["stroke-width"] || 1) * 0.75;
80116             opacity = params["stroke-opacity"] || 1;
80117             // VML Does not support stroke widths under 1, so we're going to fiddle with stroke-opacity instead.
80118             if (Ext.isNumber(width) && width < 1) {
80119                 strokeEl.weight = 1;
80120                 strokeEl.opacity = opacity * width;
80121             }
80122             else {
80123                 strokeEl.weight = width;
80124                 strokeEl.opacity = opacity;
80125             }
80126         }
80127         if (newStroke) {
80128             el.appendChild(strokeEl);
80129         }
80130     },
80131
80132     setClip: function(sprite, params) {
80133         var me = this,
80134             el = sprite.el,
80135             clipEl = sprite.clipEl,
80136             rect = String(params["clip-rect"]).split(me.separatorRe);
80137         if (!clipEl) {
80138             clipEl = sprite.clipEl = me.el.insertFirst(Ext.getDoc().dom.createElement("div"));
80139             clipEl.addCls(Ext.baseCSSPrefix + 'vml-sprite');
80140         }
80141         if (rect.length == 4) {
80142             rect[2] = +rect[2] + (+rect[0]);
80143             rect[3] = +rect[3] + (+rect[1]);
80144             clipEl.setStyle("clip", Ext.String.format("rect({1}px {2}px {3}px {0}px)", rect[0], rect[1], rect[2], rect[3]));
80145             clipEl.setSize(me.el.width, me.el.height);
80146         }
80147         else {
80148             clipEl.setStyle("clip", "");
80149         }
80150     },
80151
80152     setTextAttributes: function(sprite, params) {
80153         var me = this,
80154             vml = sprite.vml,
80155             textStyle = vml.textpath.style,
80156             spanCacheStyle = me.span.style,
80157             zoom = me.zoom,
80158             round = Math.round,
80159             fontObj = {
80160                 fontSize: "font-size",
80161                 fontWeight: "font-weight",
80162                 fontStyle: "font-style"
80163             },
80164             fontProp,
80165             paramProp;
80166         if (sprite.dirtyFont) {
80167             if (params.font) {
80168                 textStyle.font = spanCacheStyle.font = params.font;
80169             }
80170             if (params["font-family"]) {
80171                 textStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(me.fontFamilyRe, "") + '"';
80172                 spanCacheStyle.fontFamily = params["font-family"];
80173             }
80174
80175             for (fontProp in fontObj) {
80176                 paramProp = params[fontObj[fontProp]];
80177                 if (paramProp) {
80178                     textStyle[fontProp] = spanCacheStyle[fontProp] = paramProp;
80179                 }
80180             }
80181
80182             me.setText(sprite, params.text);
80183             
80184             if (vml.textpath.string) {
80185                 me.span.innerHTML = String(vml.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>");
80186             }
80187             vml.W = me.span.offsetWidth;
80188             vml.H = me.span.offsetHeight + 2; // TODO handle baseline differences and offset in VML Textpath
80189
80190             // text-anchor emulation
80191             if (params["text-anchor"] == "middle") {
80192                 textStyle["v-text-align"] = "center";
80193             }
80194             else if (params["text-anchor"] == "end") {
80195                 textStyle["v-text-align"] = "right";
80196                 vml.bbx = -Math.round(vml.W / 2);
80197             }
80198             else {
80199                 textStyle["v-text-align"] = "left";
80200                 vml.bbx = Math.round(vml.W / 2);
80201             }
80202         }
80203         vml.X = params.x;
80204         vml.Y = params.y;
80205         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);
80206         // Clear bbox cache
80207         sprite.bbox.plain = null;
80208         sprite.bbox.transform = null;
80209         sprite.dirtyFont = false;
80210     },
80211     
80212     setText: function(sprite, text) {
80213         sprite.vml.textpath.string = Ext.htmlDecode(text);
80214     },
80215
80216     hide: function() {
80217         this.el.hide();
80218     },
80219
80220     show: function() {
80221         this.el.show();
80222     },
80223
80224     hidePrim: function(sprite) {
80225         sprite.el.addCls(Ext.baseCSSPrefix + 'hide-visibility');
80226     },
80227
80228     showPrim: function(sprite) {
80229         sprite.el.removeCls(Ext.baseCSSPrefix + 'hide-visibility');
80230     },
80231
80232     setSize: function(width, height) {
80233         var me = this,
80234             viewBox = me.viewBox,
80235             scaleX, scaleY, items, i, len;
80236         width = width || me.width;
80237         height = height || me.height;
80238         me.width = width;
80239         me.height = height;
80240
80241         if (!me.el) {
80242             return;
80243         }
80244
80245         // Size outer div
80246         if (width != undefined) {
80247             me.el.setWidth(width);
80248         }
80249         if (height != undefined) {
80250             me.el.setHeight(height);
80251         }
80252
80253         // Handle viewBox sizing
80254         if (viewBox && (width || height)) {
80255             var viewBoxX = viewBox.x,
80256                 viewBoxY = viewBox.y,
80257                 viewBoxWidth = viewBox.width,
80258                 viewBoxHeight = viewBox.height,
80259                 relativeHeight = height / viewBoxHeight,
80260                 relativeWidth = width / viewBoxWidth,
80261                 size;
80262             if (viewBoxWidth * relativeHeight < width) {
80263                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
80264             }
80265             if (viewBoxHeight * relativeWidth < height) {
80266                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
80267             }
80268             size = 1 / Math.max(viewBoxWidth / width, viewBoxHeight / height);
80269             // Scale and translate group
80270             me.viewBoxShift = {
80271                 dx: -viewBoxX,
80272                 dy: -viewBoxY,
80273                 scale: size
80274             };
80275             items = me.items.items;
80276             for (i = 0, len = items.length; i < len; i++) {
80277                 me.transform(items[i]);
80278             }
80279         }
80280         this.callParent(arguments);
80281     },
80282
80283     setViewBox: function(x, y, width, height) {
80284         this.callParent(arguments);
80285         this.viewBox = {
80286             x: x,
80287             y: y,
80288             width: width,
80289             height: height
80290         };
80291     },
80292
80293     onAdd: function(item) {
80294         this.callParent(arguments);
80295         if (this.el) {
80296             this.renderItem(item);
80297         }
80298     },
80299
80300     onRemove: function(sprite) {
80301         if (sprite.el) {
80302             sprite.el.remove();
80303             delete sprite.el;
80304         }
80305         this.callParent(arguments);
80306     },
80307
80308     render: function (container) {
80309         var me = this,
80310             doc = Ext.getDoc().dom;
80311         // VML Node factory method (createNode)
80312         if (!me.createNode) {
80313             try {
80314                 if (!doc.namespaces.rvml) {
80315                     doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
80316                 }
80317                 me.createNode = function (tagName) {
80318                     return doc.createElement("<rvml:" + tagName + ' class="rvml">');
80319                 };
80320             } catch (e) {
80321                 me.createNode = function (tagName) {
80322                     return doc.createElement("<" + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
80323                 };
80324             }
80325         }
80326
80327         if (!me.el) {
80328             var el = doc.createElement("div");
80329             me.el = Ext.get(el);
80330             me.el.addCls(me.baseVmlCls);
80331
80332             // Measuring span (offscrren)
80333             me.span = doc.createElement("span");
80334             Ext.get(me.span).addCls(me.measureSpanCls);
80335             el.appendChild(me.span);
80336             me.el.setSize(me.width || 10, me.height || 10);
80337             container.appendChild(el);
80338             me.el.on({
80339                 scope: me,
80340                 mouseup: me.onMouseUp,
80341                 mousedown: me.onMouseDown,
80342                 mouseover: me.onMouseOver,
80343                 mouseout: me.onMouseOut,
80344                 mousemove: me.onMouseMove,
80345                 mouseenter: me.onMouseEnter,
80346                 mouseleave: me.onMouseLeave,
80347                 click: me.onClick
80348             });
80349         }
80350         me.renderAll();
80351     },
80352
80353     renderAll: function() {
80354         this.items.each(this.renderItem, this);
80355     },
80356
80357     redraw: function(sprite) {
80358         sprite.dirty = true;
80359         this.renderItem(sprite);
80360     },
80361
80362     renderItem: function (sprite) {
80363         // Does the surface element exist?
80364         if (!this.el) {
80365             return;
80366         }
80367
80368         // Create sprite element if necessary
80369         if (!sprite.el) {
80370             this.createSpriteElement(sprite);
80371         }
80372
80373         if (sprite.dirty) {
80374             this.applyAttrs(sprite);
80375             if (sprite.dirtyTransform) {
80376                 this.applyTransformations(sprite);
80377             }
80378         }
80379     },
80380
80381     rotationCompensation: function (deg, dx, dy) {
80382         var matrix = Ext.create('Ext.draw.Matrix');
80383         matrix.rotate(-deg, 0.5, 0.5);
80384         return {
80385             x: matrix.x(dx, dy),
80386             y: matrix.y(dx, dy)
80387         };
80388     },
80389
80390     transform: function(sprite) {
80391         var me = this,
80392             matrix = Ext.create('Ext.draw.Matrix'),
80393             transforms = sprite.transformations,
80394             transformsLength = transforms.length,
80395             i = 0,
80396             deltaDegrees = 0,
80397             deltaScaleX = 1,
80398             deltaScaleY = 1,
80399             flip = "",
80400             el = sprite.el,
80401             dom = el.dom,
80402             domStyle = dom.style,
80403             zoom = me.zoom,
80404             skew = sprite.skew,
80405             deltaX, deltaY, transform, type, compensate, y, fill, newAngle,zoomScaleX, zoomScaleY, newOrigin;
80406
80407         for (; i < transformsLength; i++) {
80408             transform = transforms[i];
80409             type = transform.type;
80410             if (type == "translate") {
80411                 matrix.translate(transform.x, transform.y);
80412             }
80413             else if (type == "rotate") {
80414                 matrix.rotate(transform.degrees, transform.x, transform.y);
80415                 deltaDegrees += transform.degrees;
80416             }
80417             else if (type == "scale") {
80418                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
80419                 deltaScaleX *= transform.x;
80420                 deltaScaleY *= transform.y;
80421             }
80422         }
80423
80424         if (me.viewBoxShift) {
80425             matrix.scale(me.viewBoxShift.scale, me.viewBoxShift.scale, -1, -1);
80426             matrix.add(1, 0, 0, 1, me.viewBoxShift.dx, me.viewBoxShift.dy);
80427         }
80428
80429         sprite.matrix = matrix;
80430
80431
80432         // Hide element while we transform
80433
80434         if (sprite.type != "image" && skew) {
80435             // matrix transform via VML skew
80436             skew.matrix = matrix.toString();
80437             skew.offset = matrix.offset();
80438         }
80439         else {
80440             deltaX = matrix.matrix[0][2];
80441             deltaY = matrix.matrix[1][2];
80442             // Scale via coordsize property
80443             zoomScaleX = zoom / deltaScaleX;
80444             zoomScaleY = zoom / deltaScaleY;
80445
80446             dom.coordsize = Math.abs(zoomScaleX) + " " + Math.abs(zoomScaleY);
80447
80448             // Rotate via rotation property
80449             newAngle = deltaDegrees * (deltaScaleX * ((deltaScaleY < 0) ? -1 : 1));
80450             if (newAngle != domStyle.rotation && !(newAngle === 0 && !domStyle.rotation)) {
80451                 domStyle.rotation = newAngle;
80452             }
80453             if (deltaDegrees) {
80454                 // Compensate x/y position due to rotation
80455                 compensate = me.rotationCompensation(deltaDegrees, deltaX, deltaY);
80456                 deltaX = compensate.x;
80457                 deltaY = compensate.y;
80458             }
80459
80460             // Handle negative scaling via flipping
80461             if (deltaScaleX < 0) {
80462                 flip += "x";
80463             }
80464             if (deltaScaleY < 0) {
80465                 flip += " y";
80466                 y = -1;
80467             }
80468             if (flip != "" && !dom.style.flip) {
80469                 domStyle.flip = flip;
80470             }
80471
80472             // Translate via coordorigin property
80473             newOrigin = (deltaX * -zoomScaleX) + " " + (deltaY * -zoomScaleY);
80474             if (newOrigin != dom.coordorigin) {
80475                 dom.coordorigin = (deltaX * -zoomScaleX) + " " + (deltaY * -zoomScaleY);
80476             }
80477         }
80478     },
80479
80480     createItem: function (config) {
80481         return Ext.create('Ext.draw.Sprite', config);
80482     },
80483
80484     getRegion: function() {
80485         return this.el.getRegion();
80486     },
80487
80488     addCls: function(sprite, className) {
80489         if (sprite && sprite.el) {
80490             sprite.el.addCls(className);
80491         }
80492     },
80493
80494     removeCls: function(sprite, className) {
80495         if (sprite && sprite.el) {
80496             sprite.el.removeCls(className);
80497         }
80498     },
80499
80500     /**
80501      * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
80502      * to its corresponding VML attributes and store it for later use by individual sprites.
80503      * @param {Object} gradient
80504      */
80505     addGradient: function(gradient) {
80506         var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
80507             colors = [],
80508             stops = Ext.create('Ext.util.MixedCollection');
80509
80510         // Build colors string
80511         stops.addAll(gradient.stops);
80512         stops.sortByKey("ASC", function(a, b) {
80513             a = parseInt(a, 10);
80514             b = parseInt(b, 10);
80515             return a > b ? 1 : (a < b ? -1 : 0);
80516         });
80517         stops.eachKey(function(k, v) {
80518             colors.push(k + "% " + v.color);
80519         });
80520
80521         gradients.add(gradient.id, {
80522             colors: colors.join(","),
80523             angle: gradient.angle
80524         });
80525     },
80526
80527     destroy: function() {
80528         var me = this;
80529         
80530         me.callParent(arguments);
80531         if (me.el) {
80532             me.el.remove();
80533         }
80534         delete me.el;
80535     }
80536 });
80537
80538 /**
80539  * @class Ext.fx.target.ElementCSS
80540  * @extends Ext.fx.target.Element
80541  * 
80542  * This class represents a animation target for an {@link Ext.core.Element} that supports CSS
80543  * based animation. In general this class will not be created directly, the {@link Ext.core.Element} 
80544  * will be passed to the animation and the appropriate target will be created.
80545  */
80546 Ext.define('Ext.fx.target.ElementCSS', {
80547
80548     /* Begin Definitions */
80549
80550     extend: 'Ext.fx.target.Element',
80551
80552     /* End Definitions */
80553
80554     setAttr: function(targetData, isFirstFrame) {
80555         var cssArr = {
80556                 attrs: [],
80557                 duration: [],
80558                 easing: []
80559             },
80560             ln = targetData.length,
80561             attributes,
80562             attrs,
80563             attr,
80564             easing,
80565             duration,
80566             o,
80567             i,
80568             j,
80569             ln2;
80570         for (i = 0; i < ln; i++) {
80571             attrs = targetData[i];
80572             duration = attrs.duration;
80573             easing = attrs.easing;
80574             attrs = attrs.attrs;
80575             for (attr in attrs) {
80576                 if (Ext.Array.indexOf(cssArr.attrs, attr) == -1) {
80577                     cssArr.attrs.push(attr.replace(/[A-Z]/g, function(v) {
80578                         return '-' + v.toLowerCase();
80579                     }));
80580                     cssArr.duration.push(duration + 'ms');
80581                     cssArr.easing.push(easing);
80582                 }
80583             }
80584         }
80585         attributes = cssArr.attrs.join(',');
80586         duration = cssArr.duration.join(',');
80587         easing = cssArr.easing.join(', ');
80588         for (i = 0; i < ln; i++) {
80589             attrs = targetData[i].attrs;
80590             for (attr in attrs) {
80591                 ln2 = attrs[attr].length;
80592                 for (j = 0; j < ln2; j++) {
80593                     o = attrs[attr][j];
80594                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', isFirstFrame ? '' : attributes);
80595                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', isFirstFrame ? '' : duration);
80596                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', isFirstFrame ? '' : easing);
80597                     o[0].setStyle(attr, o[1]);
80598
80599                     // Must trigger reflow to make this get used as the start point for the transition that follows
80600                     if (isFirstFrame) {
80601                         o = o[0].dom.offsetWidth;
80602                     }
80603                     else {
80604                         // Remove transition properties when completed.
80605                         o[0].on(Ext.supports.CSS3TransitionEnd, function() {
80606                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', null);
80607                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', null);
80608                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', null);
80609                         }, o[0], { single: true });
80610                     }
80611                 }
80612             }
80613         }
80614     }
80615 });
80616 /**
80617  * @class Ext.fx.target.CompositeElementCSS
80618  * @extends Ext.fx.target.CompositeElement
80619  * 
80620  * This class represents a animation target for a {@link Ext.CompositeElement}, where the
80621  * constituent elements support CSS based animation. It allows each {@link Ext.core.Element} in 
80622  * the group to be animated as a whole. In general this class will not be created directly, 
80623  * the {@link Ext.CompositeElement} will be passed to the animation and the appropriate target 
80624  * will be created.
80625  */
80626 Ext.define('Ext.fx.target.CompositeElementCSS', {
80627
80628     /* Begin Definitions */
80629
80630     extend: 'Ext.fx.target.CompositeElement',
80631
80632     requires: ['Ext.fx.target.ElementCSS'],
80633
80634     /* End Definitions */
80635     setAttr: function() {
80636         return Ext.fx.target.ElementCSS.prototype.setAttr.apply(this, arguments);
80637     }
80638 });
80639 /**
80640  * @class Ext.layout.container.AbstractFit
80641  * @extends Ext.layout.container.Container
80642  * <p>This is a base class for layouts that contain <b>a single item</b> that automatically expands to fill the layout's
80643  * container.  This class is intended to be extended or created via the <tt>layout:'fit'</tt> {@link Ext.container.Container#layout}
80644  * config, and should generally not need to be created directly via the new keyword.</p>
80645  * <p>FitLayout does not have any direct config options (other than inherited ones).  To fit a panel to a container
80646  * using FitLayout, simply set layout:'fit' on the container and add a single panel to it.  If the container has
80647  * multiple panels, only the first one will be displayed.  Example usage:</p>
80648  * <pre><code>
80649 var p = new Ext.panel.Panel({
80650     title: 'Fit Layout',
80651     layout:'fit',
80652     items: {
80653         title: 'Inner Panel',
80654         html: '&lt;p&gt;This is the inner panel content&lt;/p&gt;',
80655         border: false
80656     }
80657 });
80658 </code></pre>
80659  */
80660 Ext.define('Ext.layout.container.AbstractFit', {
80661
80662     /* Begin Definitions */
80663
80664     extend: 'Ext.layout.container.Container',
80665
80666     /* End Definitions */
80667
80668     itemCls: Ext.baseCSSPrefix + 'fit-item',
80669     targetCls: Ext.baseCSSPrefix + 'layout-fit',
80670     type: 'fit'
80671 });
80672 /**
80673  * @class Ext.layout.container.Fit
80674  * @extends Ext.layout.container.AbstractFit
80675  * <p>This is a base class for layouts that contain <b>a single item</b> that automatically expands to fill the layout's
80676  * container.  This class is intended to be extended or created via the <tt>layout:'fit'</tt> {@link Ext.container.Container#layout}
80677  * config, and should generally not need to be created directly via the new keyword.</p>
80678  * <p>FitLayout does not have any direct config options (other than inherited ones).  To fit a panel to a container
80679  * using FitLayout, simply set layout:'fit' on the container and add a single panel to it.  If the container has
80680  * multiple panels, only the first one will be displayed.  
80681  * {@img Ext.layout.container.Fit/Ext.layout.container.Fit.png Ext.layout.container.Fit container layout}
80682  * Example usage:</p>
80683  * <pre><code>
80684     Ext.create('Ext.panel.Panel', {
80685         title: 'Fit Layout',
80686         width: 300,
80687         height: 150,
80688         layout:'fit',
80689         items: {
80690             title: 'Inner Panel',
80691             html: 'This is the inner panel content',
80692             bodyPadding: 20,
80693             border: false
80694         },
80695         renderTo: Ext.getBody()
80696     });  
80697 </code></pre>
80698  */
80699 Ext.define('Ext.layout.container.Fit', {
80700
80701     /* Begin Definitions */
80702
80703     extend: 'Ext.layout.container.AbstractFit',
80704     alias: 'layout.fit',
80705     alternateClassName: 'Ext.layout.FitLayout',
80706
80707     /* End Definitions */
80708    
80709     // @private
80710     onLayout : function() {
80711         var me = this;
80712         me.callParent();
80713
80714         if (me.owner.items.length) {
80715             me.setItemBox(me.owner.items.get(0), me.getLayoutTargetSize());
80716         }
80717     },
80718
80719     getTargetBox : function() {
80720         return this.getLayoutTargetSize();
80721     },
80722
80723     setItemBox : function(item, box) {
80724         var me = this;
80725         if (item && box.height > 0) {
80726             if (me.isManaged('width') === true) {
80727                box.width = undefined;
80728             }
80729             if (me.isManaged('height') === true) {
80730                box.height = undefined;
80731             }
80732             me.setItemSize(item, box.width, box.height);
80733         }
80734     }
80735 });
80736 /**
80737  * @class Ext.layout.container.AbstractCard
80738  * @extends Ext.layout.container.Fit
80739  * <p>This layout manages multiple child Components, each is fit to the Container, where only a single child Component
80740  * can be visible at any given time.  This layout style is most commonly used for wizards, tab implementations, etc.
80741  * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,
80742  * and should generally not need to be created directly via the new keyword.</p>
80743  * <p>The CardLayout's focal method is {@link #setActiveItem}.  Since only one panel is displayed at a time,
80744  * the only way to move from one Component to the next is by calling setActiveItem, passing the id or index of
80745  * the next panel to display.  The layout itself does not provide a user interface for handling this navigation,
80746  * so that functionality must be provided by the developer.</p>
80747  * <p>Containers that are configured with a card layout will have a method setActiveItem dynamically added to it.
80748  * <pre><code>
80749       var p = new Ext.panel.Panel({
80750           fullscreen: true,
80751           layout: 'card',
80752           items: [{
80753               html: 'Card 1'
80754           },{
80755               html: 'Card 2'
80756           }]
80757       });
80758       p.setActiveItem(1);
80759    </code></pre>
80760  * </p>
80761  */
80762
80763 Ext.define('Ext.layout.container.AbstractCard', {
80764
80765     /* Begin Definitions */
80766
80767     extend: 'Ext.layout.container.Fit',
80768
80769     /* End Definitions */
80770
80771     type: 'card',
80772
80773     sizeAllCards: false,
80774
80775     hideInactive: true,
80776
80777     /**
80778      * @cfg {Boolean} deferredRender
80779      * True to render each contained item at the time it becomes active, false to render all contained items
80780      * as soon as the layout is rendered (defaults to false).  If there is a significant amount of content or
80781      * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
80782      * true might improve performance.
80783      */
80784     deferredRender : false,
80785
80786     beforeLayout: function() {
80787         var me = this;
80788         me.activeItem = me.getActiveItem();
80789         if (me.activeItem && me.deferredRender) {
80790             me.renderItems([me.activeItem], me.getRenderTarget());
80791             return true;
80792         }
80793         else {
80794             return this.callParent(arguments);
80795         }
80796     },
80797
80798     onLayout: function() {
80799         var me = this,
80800             activeItem = me.activeItem,
80801             items = me.getVisibleItems(),
80802             ln = items.length,
80803             targetBox = me.getTargetBox(),
80804             i, item;
80805
80806         for (i = 0; i < ln; i++) {
80807             item = items[i];
80808             me.setItemBox(item, targetBox);
80809         }
80810
80811         if (!me.firstActivated && activeItem) {
80812             if (activeItem.fireEvent('beforeactivate', activeItem) !== false) {
80813                 activeItem.fireEvent('activate', activeItem);
80814             }
80815             me.firstActivated = true;
80816         }
80817     },
80818
80819     isValidParent : function(item, target, position) {
80820         // Note: Card layout does not care about order within the target because only one is ever visible.
80821         // We only care whether the item is a direct child of the target.
80822         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
80823         return (itemEl && itemEl.parentNode === (target.dom || target)) || false;
80824     },
80825
80826     /**
80827      * Return the active (visible) component in the layout.
80828      * @returns {Ext.Component}
80829      */
80830     getActiveItem: function() {
80831         var me = this;
80832         if (!me.activeItem && me.owner) {
80833             me.activeItem = me.parseActiveItem(me.owner.activeItem);
80834         }
80835
80836         if (me.activeItem && me.owner.items.indexOf(me.activeItem) != -1) {
80837             return me.activeItem;
80838         }
80839
80840         return null;
80841     },
80842
80843     // @private
80844     parseActiveItem: function(item) {
80845         if (item && item.isComponent) {
80846             return item;
80847         }
80848         else if (typeof item == 'number' || item === undefined) {
80849             return this.getLayoutItems()[item || 0];
80850         }
80851         else {
80852             return this.owner.getComponent(item);
80853         }
80854     },
80855
80856     // @private
80857     configureItem: function(item, position) {
80858         this.callParent([item, position]);
80859         if (this.hideInactive && this.activeItem !== item) {
80860             item.hide();
80861         }
80862         else {
80863             item.show();
80864         }
80865     },
80866
80867     onRemove: function(component) {
80868         if (component === this.activeItem) {
80869             this.activeItem = null;
80870             if (this.owner.items.getCount() === 0) {
80871                 this.firstActivated = false;
80872             }
80873         }
80874     },
80875
80876     // @private
80877     getAnimation: function(newCard, owner) {
80878         var newAnim = (newCard || {}).cardSwitchAnimation;
80879         if (newAnim === false) {
80880             return false;
80881         }
80882         return newAnim || owner.cardSwitchAnimation;
80883     },
80884
80885     /**
80886      * Return the active (visible) component in the layout to the next card
80887      * @returns {Ext.Component}
80888      */
80889     getNext: function(wrap) {
80890         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This 
80891         //should come back in 4.1
80892         
80893         var items = this.getLayoutItems(),
80894             index = Ext.Array.indexOf(items, this.activeItem);
80895         return items[index + 1] || (wrap ? items[0] : false);
80896     },
80897
80898     /**
80899      * Sets the active (visible) component in the layout to the next card
80900      */
80901     next: function(anim, wrap) {
80902         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This 
80903         //should come back in 4.1
80904         
80905         return this.setActiveItem(this.getNext(wrap), anim);
80906     },
80907
80908     /**
80909      * Return the active (visible) component in the layout to the previous card
80910      * @returns {Ext.Component}
80911      */
80912     getPrev: function(wrap) {
80913         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This 
80914         //should come back in 4.1
80915         
80916         var items = this.getLayoutItems(),
80917             index = Ext.Array.indexOf(items, this.activeItem);
80918         return items[index - 1] || (wrap ? items[items.length - 1] : false);
80919     },
80920
80921     /**
80922      * Sets the active (visible) component in the layout to the previous card
80923      */
80924     prev: function(anim, wrap) {
80925         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This 
80926         //should come back in 4.1
80927         
80928         return this.setActiveItem(this.getPrev(wrap), anim);
80929     }
80930 });
80931
80932 /**
80933  * @class Ext.selection.Model
80934  * @extends Ext.util.Observable
80935  *
80936  * Tracks what records are currently selected in a databound widget.
80937  *
80938  * This is an abstract class and is not meant to be directly used.
80939  *
80940  * DataBound UI widgets such as GridPanel, TreePanel, and ListView
80941  * should subclass AbstractStoreSelectionModel and provide a way
80942  * to binding to the component.
80943  *
80944  * The abstract methods onSelectChange and onLastFocusChanged should
80945  * be implemented in these subclasses to update the UI widget.
80946  */
80947 Ext.define('Ext.selection.Model', {
80948     extend: 'Ext.util.Observable',
80949     alternateClassName: 'Ext.AbstractStoreSelectionModel',
80950     requires: ['Ext.data.StoreManager'],
80951     // lastSelected
80952
80953     /**
80954      * @cfg {String} mode
80955      * Modes of selection.
80956      * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'SINGLE'
80957      */
80958     
80959     /**
80960      * @cfg {Boolean} allowDeselect
80961      * Allow users to deselect a record in a DataView, List or Grid. Only applicable when the SelectionModel's mode is 'SINGLE'. Defaults to false.
80962      */
80963     allowDeselect: false,
80964
80965     /**
80966      * @property selected
80967      * READ-ONLY A MixedCollection that maintains all of the currently selected
80968      * records.
80969      */
80970     selected: null,
80971     
80972     
80973     /**
80974      * Prune records when they are removed from the store from the selection.
80975      * This is a private flag. For an example of its usage, take a look at
80976      * Ext.selection.TreeModel.
80977      * @private
80978      */
80979     pruneRemoved: true,
80980
80981     constructor: function(cfg) {
80982         var me = this;
80983         
80984         cfg = cfg || {};
80985         Ext.apply(me, cfg);
80986         
80987         me.addEvents(
80988             /**
80989              * @event selectionchange
80990              * Fired after a selection change has occurred
80991              * @param {Ext.selection.Model} this
80992              * @param  {Array} selected The selected records
80993              */
80994              'selectionchange'
80995         );
80996
80997         me.modes = {
80998             SINGLE: true,
80999             SIMPLE: true,
81000             MULTI: true
81001         };
81002
81003         // sets this.selectionMode
81004         me.setSelectionMode(cfg.mode || me.mode);
81005
81006         // maintains the currently selected records.
81007         me.selected = Ext.create('Ext.util.MixedCollection');
81008         
81009         me.callParent(arguments);
81010     },
81011
81012     // binds the store to the selModel.
81013     bind : function(store, initial){
81014         var me = this;
81015         
81016         if(!initial && me.store){
81017             if(store !== me.store && me.store.autoDestroy){
81018                 me.store.destroy();
81019             }else{
81020                 me.store.un("add", me.onStoreAdd, me);
81021                 me.store.un("clear", me.onStoreClear, me);
81022                 me.store.un("remove", me.onStoreRemove, me);
81023                 me.store.un("update", me.onStoreUpdate, me);
81024             }
81025         }
81026         if(store){
81027             store = Ext.data.StoreManager.lookup(store);
81028             store.on({
81029                 add: me.onStoreAdd,
81030                 clear: me.onStoreClear,
81031                 remove: me.onStoreRemove,
81032                 update: me.onStoreUpdate,
81033                 scope: me
81034             });
81035         }
81036         me.store = store;
81037         if(store && !initial) {
81038             me.refresh();
81039         }
81040     },
81041
81042     /**
81043      * Select all records in the view.
81044      * @param {Boolean} suppressEvent True to suppress any selects event
81045      */
81046     selectAll: function(suppressEvent) {
81047         var me = this,
81048             selections = me.store.getRange(),
81049             i = 0,
81050             len = selections.length,
81051             start = me.getSelection().length;
81052             
81053         me.bulkChange = true;
81054         for (; i < len; i++) {
81055             me.doSelect(selections[i], true, suppressEvent);
81056         }
81057         delete me.bulkChange;
81058         // fire selection change only if the number of selections differs
81059         me.maybeFireSelectionChange(me.getSelection().length !== start);
81060     },
81061
81062     /**
81063      * Deselect all records in the view.
81064      * @param {Boolean} suppressEvent True to suppress any deselect events
81065      */
81066     deselectAll: function(suppressEvent) {
81067         var me = this,
81068             selections = me.getSelection(),
81069             i = 0,
81070             len = selections.length,
81071             start = me.getSelection().length;
81072             
81073         me.bulkChange = true;
81074         for (; i < len; i++) {
81075             me.doDeselect(selections[i], suppressEvent);
81076         }
81077         delete me.bulkChange;
81078         // fire selection change only if the number of selections differs
81079         me.maybeFireSelectionChange(me.getSelection().length !== start);
81080     },
81081
81082     // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
81083     // selection modes. Requires that an event be passed so that we can know
81084     // if user held ctrl or shift.
81085     selectWithEvent: function(record, e) {
81086         var me = this;
81087         
81088         switch (me.selectionMode) {
81089             case 'MULTI':
81090                 if (e.ctrlKey && me.isSelected(record)) {
81091                     me.doDeselect(record, false);
81092                 } else if (e.shiftKey && me.lastFocused) {
81093                     me.selectRange(me.lastFocused, record, e.ctrlKey);
81094                 } else if (e.ctrlKey) {
81095                     me.doSelect(record, true, false);
81096                 } else if (me.isSelected(record) && !e.shiftKey && !e.ctrlKey && me.selected.getCount() > 1) {
81097                     me.doSelect(record, false, false);
81098                 } else {
81099                     me.doSelect(record, false);
81100                 }
81101                 break;
81102             case 'SIMPLE':
81103                 if (me.isSelected(record)) {
81104                     me.doDeselect(record);
81105                 } else {
81106                     me.doSelect(record, true);
81107                 }
81108                 break;
81109             case 'SINGLE':
81110                 // if allowDeselect is on and this record isSelected, deselect it
81111                 if (me.allowDeselect && me.isSelected(record)) {
81112                     me.doDeselect(record);
81113                 // select the record and do NOT maintain existing selections
81114                 } else {
81115                     me.doSelect(record, false);
81116                 }
81117                 break;
81118         }
81119     },
81120
81121     /**
81122      * Selects a range of rows if the selection model {@link #isLocked is not locked}.
81123      * All rows in between startRow and endRow are also selected.
81124      * @param {Ext.data.Model/Number} startRow The record or index of the first row in the range
81125      * @param {Ext.data.Model/Number} endRow The record or index of the last row in the range
81126      * @param {Boolean} keepExisting (optional) True to retain existing selections
81127      */
81128     selectRange : function(startRow, endRow, keepExisting, dir){
81129         var me = this,
81130             store = me.store,
81131             selectedCount = 0,
81132             i,
81133             tmp,
81134             dontDeselect,
81135             records = [];
81136         
81137         if (me.isLocked()){
81138             return;
81139         }
81140         
81141         if (!keepExisting) {
81142             me.clearSelections();
81143         }
81144         
81145         if (!Ext.isNumber(startRow)) {
81146             startRow = store.indexOf(startRow);
81147         } 
81148         if (!Ext.isNumber(endRow)) {
81149             endRow = store.indexOf(endRow);
81150         }
81151         
81152         // swap values
81153         if (startRow > endRow){
81154             tmp = endRow;
81155             endRow = startRow;
81156             startRow = tmp;
81157         }
81158
81159         for (i = startRow; i <= endRow; i++) {
81160             if (me.isSelected(store.getAt(i))) {
81161                 selectedCount++;
81162             }
81163         }
81164
81165         if (!dir) {
81166             dontDeselect = -1;
81167         } else {
81168             dontDeselect = (dir == 'up') ? startRow : endRow;
81169         }
81170         
81171         for (i = startRow; i <= endRow; i++){
81172             if (selectedCount == (endRow - startRow + 1)) {
81173                 if (i != dontDeselect) {
81174                     me.doDeselect(i, true);
81175                 }
81176             } else {
81177                 records.push(store.getAt(i));
81178             }
81179         }
81180         me.doMultiSelect(records, true);
81181     },
81182     
81183     /**
81184      * Selects a record instance by record instance or index.
81185      * @param {Ext.data.Model/Index} records An array of records or an index
81186      * @param {Boolean} keepExisting
81187      * @param {Boolean} suppressEvent Set to false to not fire a select event
81188      */
81189     select: function(records, keepExisting, suppressEvent) {
81190         this.doSelect(records, keepExisting, suppressEvent);
81191     },
81192
81193     /**
81194      * Deselects a record instance by record instance or index.
81195      * @param {Ext.data.Model/Index} records An array of records or an index
81196      * @param {Boolean} suppressEvent Set to false to not fire a deselect event
81197      */
81198     deselect: function(records, suppressEvent) {
81199         this.doDeselect(records, suppressEvent);
81200     },
81201     
81202     doSelect: function(records, keepExisting, suppressEvent) {
81203         var me = this,
81204             record;
81205             
81206         if (me.locked) {
81207             return;
81208         }
81209         if (typeof records === "number") {
81210             records = [me.store.getAt(records)];
81211         }
81212         if (me.selectionMode == "SINGLE" && records) {
81213             record = records.length ? records[0] : records;
81214             me.doSingleSelect(record, suppressEvent);
81215         } else {
81216             me.doMultiSelect(records, keepExisting, suppressEvent);
81217         }
81218     },
81219
81220     doMultiSelect: function(records, keepExisting, suppressEvent) {
81221         var me = this,
81222             selected = me.selected,
81223             change = false,
81224             i = 0,
81225             len, record;
81226             
81227         if (me.locked) {
81228             return;
81229         }
81230         
81231
81232         records = !Ext.isArray(records) ? [records] : records;
81233         len = records.length;
81234         if (!keepExisting && selected.getCount() > 0) {
81235             change = true;
81236             me.doDeselect(me.getSelection(), suppressEvent);
81237         }
81238
81239         for (; i < len; i++) {
81240             record = records[i];
81241             if (keepExisting && me.isSelected(record)) {
81242                 continue;
81243             }
81244             change = true;
81245             me.lastSelected = record;
81246             selected.add(record);
81247
81248             me.onSelectChange(record, true, suppressEvent);
81249         }
81250         me.setLastFocused(record, suppressEvent);
81251         // fire selchange if there was a change and there is no suppressEvent flag
81252         me.maybeFireSelectionChange(change && !suppressEvent);
81253     },
81254
81255     // records can be an index, a record or an array of records
81256     doDeselect: function(records, suppressEvent) {
81257         var me = this,
81258             selected = me.selected,
81259             change = false,
81260             i = 0,
81261             len, record;
81262             
81263         if (me.locked) {
81264             return;
81265         }
81266
81267         if (typeof records === "number") {
81268             records = [me.store.getAt(records)];
81269         }
81270
81271         records = !Ext.isArray(records) ? [records] : records;
81272         len = records.length;
81273         for (; i < len; i++) {
81274             record = records[i];
81275             if (selected.remove(record)) {
81276                 if (me.lastSelected == record) {
81277                     me.lastSelected = selected.last();
81278                 }
81279                 me.onSelectChange(record, false, suppressEvent);
81280                 change = true;
81281             }
81282         }
81283         // fire selchange if there was a change and there is no suppressEvent flag
81284         me.maybeFireSelectionChange(change && !suppressEvent);
81285     },
81286
81287     doSingleSelect: function(record, suppressEvent) {
81288         var me = this,
81289             selected = me.selected;
81290             
81291         if (me.locked) {
81292             return;
81293         }
81294         // already selected.
81295         // should we also check beforeselect?
81296         if (me.isSelected(record)) {
81297             return;
81298         }
81299         if (selected.getCount() > 0) {
81300             me.doDeselect(me.lastSelected, suppressEvent);
81301         }
81302         selected.add(record);
81303         me.lastSelected = record;
81304         me.onSelectChange(record, true, suppressEvent);
81305         if (!suppressEvent) {
81306             me.setLastFocused(record);
81307         }
81308         me.maybeFireSelectionChange(!suppressEvent);
81309     },
81310
81311     /**
81312      * @param {Ext.data.Model} record
81313      * Set a record as the last focused record. This does NOT mean
81314      * that the record has been selected.
81315      */
81316     setLastFocused: function(record, supressFocus) {
81317         var me = this,
81318             recordBeforeLast = me.lastFocused;
81319         me.lastFocused = record;
81320         me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
81321     },
81322     
81323     /**
81324      * Determines if this record is currently focused.
81325      * @param Ext.data.Record record
81326      */
81327     isFocused: function(record) {
81328         return record === this.getLastFocused();
81329     },
81330
81331
81332     // fire selection change as long as true is not passed
81333     // into maybeFireSelectionChange
81334     maybeFireSelectionChange: function(fireEvent) {
81335         var me = this;
81336         if (fireEvent && !me.bulkChange) {
81337             me.fireEvent('selectionchange', me, me.getSelection());
81338         }
81339     },
81340
81341     /**
81342      * Returns the last selected record.
81343      */
81344     getLastSelected: function() {
81345         return this.lastSelected;
81346     },
81347     
81348     getLastFocused: function() {
81349         return this.lastFocused;
81350     },
81351
81352     /**
81353      * Returns an array of the currently selected records.
81354      */
81355     getSelection: function() {
81356         return this.selected.getRange();
81357     },
81358
81359     /**
81360      * Returns the current selectionMode. SINGLE, MULTI or SIMPLE.
81361      */
81362     getSelectionMode: function() {
81363         return this.selectionMode;
81364     },
81365
81366     /**
81367      * Sets the current selectionMode. SINGLE, MULTI or SIMPLE.
81368      */
81369     setSelectionMode: function(selMode) {
81370         selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
81371         // set to mode specified unless it doesnt exist, in that case
81372         // use single.
81373         this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
81374     },
81375
81376     /**
81377      * Returns true if the selections are locked.
81378      * @return {Boolean}
81379      */
81380     isLocked: function() {
81381         return this.locked;
81382     },
81383
81384     /**
81385      * Locks the current selection and disables any changes from
81386      * happening to the selection.
81387      * @param {Boolean} locked
81388      */
81389     setLocked: function(locked) {
81390         this.locked = !!locked;
81391     },
81392
81393     /**
81394      * Returns <tt>true</tt> if the specified row is selected.
81395      * @param {Record/Number} record The record or index of the record to check
81396      * @return {Boolean}
81397      */
81398     isSelected: function(record) {
81399         record = Ext.isNumber(record) ? this.store.getAt(record) : record;
81400         return this.selected.indexOf(record) !== -1;
81401     },
81402     
81403     /**
81404      * Returns true if there is a selected record.
81405      * @return {Boolean}
81406      */
81407     hasSelection: function() {
81408         return this.selected.getCount() > 0;
81409     },
81410
81411     refresh: function() {
81412         var me = this,
81413             toBeSelected = [],
81414             oldSelections = me.getSelection(),
81415             len = oldSelections.length,
81416             selection,
81417             change,
81418             i = 0,
81419             lastFocused = this.getLastFocused();
81420
81421         // check to make sure that there are no records
81422         // missing after the refresh was triggered, prune
81423         // them from what is to be selected if so
81424         for (; i < len; i++) {
81425             selection = oldSelections[i];
81426             if (!this.pruneRemoved || me.store.indexOf(selection) !== -1) {
81427                 toBeSelected.push(selection);
81428             }
81429         }
81430
81431         // there was a change from the old selected and
81432         // the new selection
81433         if (me.selected.getCount() != toBeSelected.length) {
81434             change = true;
81435         }
81436
81437         me.clearSelections();
81438         
81439         if (me.store.indexOf(lastFocused) !== -1) {
81440             // restore the last focus but supress restoring focus
81441             this.setLastFocused(lastFocused, true);
81442         }
81443
81444         if (toBeSelected.length) {
81445             // perform the selection again
81446             me.doSelect(toBeSelected, false, true);
81447         }
81448         
81449         me.maybeFireSelectionChange(change);
81450     },
81451
81452     /**
81453      * A fast reset of the selections without firing events, updating the ui, etc.
81454      * For private usage only.
81455      * @private
81456      */
81457     clearSelections: function() {
81458         // reset the entire selection to nothing
81459         var me = this;
81460         me.selected.clear();
81461         me.lastSelected = null;
81462         me.setLastFocused(null);
81463     },
81464
81465     // when a record is added to a store
81466     onStoreAdd: function() {
81467
81468     },
81469
81470     // when a store is cleared remove all selections
81471     // (if there were any)
81472     onStoreClear: function() {
81473         var me = this,
81474             selected = this.selected;
81475             
81476         if (selected.getCount > 0) {
81477             selected.clear();
81478             me.lastSelected = null;
81479             me.setLastFocused(null);
81480             me.maybeFireSelectionChange(true);
81481         }
81482     },
81483
81484     // prune records from the SelectionModel if
81485     // they were selected at the time they were
81486     // removed.
81487     onStoreRemove: function(store, record) {
81488         var me = this,
81489             selected = me.selected;
81490             
81491         if (me.locked || !me.pruneRemoved) {
81492             return;
81493         }
81494
81495         if (selected.remove(record)) {
81496             if (me.lastSelected == record) {
81497                 me.lastSelected = null;
81498             }
81499             if (me.getLastFocused() == record) {
81500                 me.setLastFocused(null);
81501             }
81502             me.maybeFireSelectionChange(true);
81503         }
81504     },
81505
81506     getCount: function() {
81507         return this.selected.getCount();
81508     },
81509
81510     // cleanup.
81511     destroy: function() {
81512
81513     },
81514
81515     // if records are updated
81516     onStoreUpdate: function() {
81517
81518     },
81519
81520     // @abstract
81521     onSelectChange: function(record, isSelected, suppressEvent) {
81522
81523     },
81524
81525     // @abstract
81526     onLastFocusChanged: function(oldFocused, newFocused) {
81527
81528     },
81529
81530     // @abstract
81531     onEditorKey: function(field, e) {
81532
81533     },
81534
81535     // @abstract
81536     bindComponent: function(cmp) {
81537
81538     }
81539 });
81540 /**
81541  * @class Ext.selection.DataViewModel
81542  * @ignore
81543  */
81544 Ext.define('Ext.selection.DataViewModel', {
81545     extend: 'Ext.selection.Model',
81546     
81547     requires: ['Ext.util.KeyNav'],
81548
81549     deselectOnContainerClick: true,
81550     
81551     /**
81552      * @cfg {Boolean} enableKeyNav
81553      * 
81554      * Turns on/off keyboard navigation within the DataView. Defaults to true.
81555      */
81556     enableKeyNav: true,
81557     
81558     constructor: function(cfg){
81559         this.addEvents(
81560             /**
81561              * @event deselect
81562              * Fired after a record is deselected
81563              * @param {Ext.selection.DataViewModel} this
81564              * @param  {Ext.data.Model} record The deselected record
81565              */
81566             'deselect',
81567             
81568             /**
81569              * @event select
81570              * Fired after a record is selected
81571              * @param {Ext.selection.DataViewModel} this
81572              * @param  {Ext.data.Model} record The selected record
81573              */
81574             'select'
81575         );
81576         this.callParent(arguments);
81577     },
81578     
81579     bindComponent: function(view) {
81580         var me = this,
81581             eventListeners = {
81582                 refresh: me.refresh,
81583                 scope: me
81584             };
81585
81586         me.view = view;
81587         me.bind(view.getStore());
81588
81589         view.on(view.triggerEvent, me.onItemClick, me);
81590         view.on(view.triggerCtEvent, me.onContainerClick, me);
81591
81592         view.on(eventListeners);
81593
81594         if (me.enableKeyNav) {
81595             me.initKeyNav(view);
81596         }
81597     },
81598
81599     onItemClick: function(view, record, item, index, e) {
81600         this.selectWithEvent(record, e);
81601     },
81602
81603     onContainerClick: function() {
81604         if (this.deselectOnContainerClick) {
81605             this.deselectAll();
81606         }
81607     },
81608     
81609     initKeyNav: function(view) {
81610         var me = this;
81611         
81612         if (!view.rendered) {
81613             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
81614             return;
81615         }
81616         
81617         view.el.set({
81618             tabIndex: -1
81619         });
81620         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
81621             down: Ext.pass(me.onNavKey, [1], me),
81622             right: Ext.pass(me.onNavKey, [1], me),
81623             left: Ext.pass(me.onNavKey, [-1], me),
81624             up: Ext.pass(me.onNavKey, [-1], me),
81625             scope: me
81626         });
81627     },
81628     
81629     onNavKey: function(step) {
81630         step = step || 1;
81631         var me = this,
81632             view = me.view,
81633             selected = me.getSelection()[0],
81634             numRecords = me.view.store.getCount(),
81635             idx;
81636                 
81637         if (selected) {
81638             idx = view.indexOf(view.getNode(selected)) + step;
81639         } else {
81640             idx = 0;
81641         }
81642         
81643         if (idx < 0) {
81644             idx = numRecords - 1;
81645         } else if (idx >= numRecords) {
81646             idx = 0;
81647         }
81648         
81649         me.select(idx);
81650     },
81651
81652     // Allow the DataView to update the ui
81653     onSelectChange: function(record, isSelected, suppressEvent) {
81654         var me = this,
81655             view = me.view,
81656             allowSelect = true;
81657         
81658         if (isSelected) {
81659             if (!suppressEvent) {
81660                 allowSelect = me.fireEvent('beforeselect', me, record) !== false;
81661             }
81662             if (allowSelect) {
81663                 view.onItemSelect(record);
81664                 if (!suppressEvent) {
81665                     me.fireEvent('select', me, record);
81666                 }
81667             }
81668         } else {
81669             view.onItemDeselect(record);
81670             if (!suppressEvent) {
81671                 me.fireEvent('deselect', me, record);
81672             }
81673         }
81674     }
81675 });
81676
81677 /**
81678  * @class Ext.state.CookieProvider
81679  * @extends Ext.state.Provider
81680  * A Provider implementation which saves and retrieves state via cookies.
81681  * The CookieProvider supports the usual cookie options, such as:
81682  * <ul>
81683  * <li>{@link #path}</li>
81684  * <li>{@link #expires}</li>
81685  * <li>{@link #domain}</li>
81686  * <li>{@link #secure}</li>
81687  * </ul>
81688  <pre><code>
81689    var cp = new Ext.state.CookieProvider({
81690        path: "/cgi-bin/",
81691        expires: new Date(new Date().getTime()+(1000*60*60*24*30)), //30 days
81692        domain: "sencha.com"
81693    });
81694    Ext.state.Manager.setProvider(cp);
81695  </code></pre>
81696  
81697  
81698  * @cfg {String} path The path for which the cookie is active (defaults to root '/' which makes it active for all pages in the site)
81699  * @cfg {Date} expires The cookie expiration date (defaults to 7 days from now)
81700  * @cfg {String} domain The domain to save the cookie for.  Note that you cannot specify a different domain than
81701  * your page is on, but you can specify a sub-domain, or simply the domain itself like 'sencha.com' to include
81702  * all sub-domains if you need to access cookies across different sub-domains (defaults to null which uses the same
81703  * domain the page is running on including the 'www' like 'www.sencha.com')
81704  * @cfg {Boolean} secure True if the site is using SSL (defaults to false)
81705  * @constructor
81706  * Create a new CookieProvider
81707  * @param {Object} config The configuration object
81708  */
81709 Ext.define('Ext.state.CookieProvider', {
81710     extend: 'Ext.state.Provider',
81711
81712     constructor : function(config){
81713         var me = this;
81714         me.path = "/";
81715         me.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
81716         me.domain = null;
81717         me.secure = false;
81718         me.callParent(arguments);
81719         me.state = me.readCookies();
81720     },
81721     
81722     // private
81723     set : function(name, value){
81724         var me = this;
81725         
81726         if(typeof value == "undefined" || value === null){
81727             me.clear(name);
81728             return;
81729         }
81730         me.setCookie(name, value);
81731         me.callParent(arguments);
81732     },
81733
81734     // private
81735     clear : function(name){
81736         this.clearCookie(name);
81737         this.callParent(arguments);
81738     },
81739
81740     // private
81741     readCookies : function(){
81742         var cookies = {},
81743             c = document.cookie + ";",
81744             re = /\s?(.*?)=(.*?);/g,
81745             prefix = this.prefix,
81746             len = prefix.length,
81747             matches,
81748             name,
81749             value;
81750             
81751         while((matches = re.exec(c)) != null){
81752             name = matches[1];
81753             value = matches[2];
81754             if (name && name.substring(0, len) == prefix){
81755                 cookies[name.substr(len)] = this.decodeValue(value);
81756             }
81757         }
81758         return cookies;
81759     },
81760
81761     // private
81762     setCookie : function(name, value){
81763         var me = this;
81764         
81765         document.cookie = me.prefix + name + "=" + me.encodeValue(value) +
81766            ((me.expires == null) ? "" : ("; expires=" + me.expires.toGMTString())) +
81767            ((me.path == null) ? "" : ("; path=" + me.path)) +
81768            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
81769            ((me.secure == true) ? "; secure" : "");
81770     },
81771
81772     // private
81773     clearCookie : function(name){
81774         var me = this;
81775         
81776         document.cookie = me.prefix + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
81777            ((me.path == null) ? "" : ("; path=" + me.path)) +
81778            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
81779            ((me.secure == true) ? "; secure" : "");
81780     }
81781 });
81782
81783 Ext.define('Ext.state.LocalStorageProvider', {
81784     /* Begin Definitions */
81785     
81786     extend: 'Ext.state.Provider',
81787     
81788     alias: 'state.localstorage',
81789     
81790     /* End Definitions */
81791    
81792     constructor: function(){
81793         var me = this;
81794         me.callParent(arguments);
81795         me.store = me.getStorageObject();
81796         me.state = me.readLocalStorage();
81797     },
81798     
81799     readLocalStorage: function(){
81800         var store = this.store,
81801             i = 0,
81802             len = store.length,
81803             prefix = this.prefix,
81804             prefixLen = prefix.length,
81805             data = {},
81806             key;
81807             
81808         for (; i < len; ++i) {
81809             key = store.key(i);
81810             if (key.substring(0, prefixLen) == prefix) {
81811                 data[key.substr(prefixLen)] = this.decodeValue(store.getItem(key));
81812             }            
81813         }
81814         return data;
81815     },
81816     
81817     set : function(name, value){
81818         var me = this;
81819         
81820         me.clear(name);
81821         if (typeof value == "undefined" || value === null) {
81822             return;
81823         }
81824         me.store.setItem(me.prefix + name, me.encodeValue(value));
81825         me.callParent(arguments);
81826     },
81827
81828     // private
81829     clear : function(name){
81830         this.store.removeItem(this.prefix + name);
81831         this.callParent(arguments);
81832     },
81833     
81834     getStorageObject: function(){
81835         try {
81836             var supports = 'localStorage' in window && window['localStorage'] !== null;
81837             if (supports) {
81838                 return window.localStorage;
81839             }
81840         } catch (e) {
81841             return false;
81842         }
81843         Ext.Error.raise('LocalStorage is not supported by the current browser');
81844     }    
81845 });
81846
81847 /**
81848  * @class Ext.util.Point
81849  * @extends Ext.util.Region
81850  *
81851  * Represents a 2D point with x and y properties, useful for comparison and instantiation
81852  * from an event:
81853  * <pre><code>
81854  * var point = Ext.util.Point.fromEvent(e);
81855  * </code></pre>
81856  */
81857
81858 Ext.define('Ext.util.Point', {
81859
81860     /* Begin Definitions */
81861     extend: 'Ext.util.Region',
81862
81863     statics: {
81864
81865         /**
81866          * Returns a new instance of Ext.util.Point base on the pageX / pageY values of the given event
81867          * @static
81868          * @param {Event} e The event
81869          * @returns Ext.util.Point
81870          */
81871         fromEvent: function(e) {
81872             e = (e.changedTouches && e.changedTouches.length > 0) ? e.changedTouches[0] : e;
81873             return new this(e.pageX, e.pageY);
81874         }
81875     },
81876
81877     /* End Definitions */
81878
81879     constructor: function(x, y) {
81880         this.callParent([y, x, y, x]);
81881     },
81882
81883     /**
81884      * Returns a human-eye-friendly string that represents this point,
81885      * useful for debugging
81886      * @return {String}
81887      */
81888     toString: function() {
81889         return "Point[" + this.x + "," + this.y + "]";
81890     },
81891
81892     /**
81893      * Compare this point and another point
81894      * @param {Ext.util.Point/Object} The point to compare with, either an instance
81895      * of Ext.util.Point or an object with left and top properties
81896      * @return {Boolean} Returns whether they are equivalent
81897      */
81898     equals: function(p) {
81899         return (this.x == p.x && this.y == p.y);
81900     },
81901
81902     /**
81903      * Whether the given point is not away from this point within the given threshold amount.
81904      * TODO: Rename this isNear.
81905      * @param {Ext.util.Point/Object} The point to check with, either an instance
81906      * of Ext.util.Point or an object with left and top properties
81907      * @param {Object/Number} threshold Can be either an object with x and y properties or a number
81908      * @return {Boolean}
81909      */
81910     isWithin: function(p, threshold) {
81911         if (!Ext.isObject(threshold)) {
81912             threshold = {
81913                 x: threshold,
81914                 y: threshold
81915             };
81916         }
81917
81918         return (this.x <= p.x + threshold.x && this.x >= p.x - threshold.x &&
81919                 this.y <= p.y + threshold.y && this.y >= p.y - threshold.y);
81920     },
81921
81922     /**
81923      * Compare this point with another point when the x and y values of both points are rounded. E.g:
81924      * [100.3,199.8] will equals to [100, 200]
81925      * @param {Ext.util.Point/Object} The point to compare with, either an instance
81926      * of Ext.util.Point or an object with x and y properties
81927      * @return {Boolean}
81928      */
81929     roundedEquals: function(p) {
81930         return (Math.round(this.x) == Math.round(p.x) && Math.round(this.y) == Math.round(p.y));
81931     }
81932 }, function() {
81933     /**
81934      * Translate this region by the given offset amount. TODO: Either use translate or translateBy!
81935      * @param {Ext.util.Offset/Object} offset Object containing the <code>x</code> and <code>y</code> properties.
81936      * Or the x value is using the two argument form.
81937      * @param {Number} The y value unless using an Offset object.
81938      * @return {Ext.util.Region} this This Region
81939      * @method
81940      */
81941     this.prototype.translate = Ext.util.Region.prototype.translateBy;
81942 });
81943
81944 /**
81945  * @class Ext.view.AbstractView
81946  * @extends Ext.Component
81947  * This is an abstract superclass and should not be used directly. Please see {@link Ext.view.View}.
81948  */
81949 Ext.define('Ext.view.AbstractView', {
81950     extend: 'Ext.Component',
81951     alternateClassName: 'Ext.view.AbstractView',
81952     requires: [
81953         'Ext.LoadMask',
81954         'Ext.data.StoreManager',
81955         'Ext.CompositeElementLite',
81956         'Ext.DomQuery',
81957         'Ext.selection.DataViewModel'
81958     ],
81959     
81960     inheritableStatics: {
81961         getRecord: function(node) {
81962             return this.getBoundView(node).getRecord(node);
81963         },
81964         
81965         getBoundView: function(node) {
81966             return Ext.getCmp(node.boundView);
81967         }
81968     },
81969     
81970     /**
81971      * @cfg {String/Array/Ext.XTemplate} tpl
81972      * @required
81973      * The HTML fragment or an array of fragments that will make up the template used by this DataView.  This should
81974      * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
81975      */
81976     /**
81977      * @cfg {Ext.data.Store} store
81978      * @required
81979      * The {@link Ext.data.Store} to bind this DataView to.
81980      */
81981
81982     /**
81983      * @cfg {String} itemSelector
81984      * @required
81985      * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
81986      * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
81987      * working with. The itemSelector is used to map DOM nodes to records. As such, there should
81988      * only be one root level element that matches the selector for each record.
81989      */
81990     
81991     /**
81992      * @cfg {String} itemCls
81993      * Specifies the class to be assigned to each element in the view when used in conjunction with the
81994      * {@link #itemTpl} configuration.
81995      */
81996     itemCls: Ext.baseCSSPrefix + 'dataview-item',
81997     
81998     /**
81999      * @cfg {String/Array/Ext.XTemplate} itemTpl
82000      * The inner portion of the item template to be rendered. Follows an XTemplate
82001      * structure and will be placed inside of a tpl.
82002      */
82003
82004     /**
82005      * @cfg {String} overItemCls
82006      * A CSS class to apply to each item in the view on mouseover (defaults to undefined). 
82007      * Ensure {@link #trackOver} is set to `true` to make use of this.
82008      */
82009
82010     /**
82011      * @cfg {String} loadingText
82012      * A string to display during data load operations (defaults to undefined).  If specified, this text will be
82013      * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
82014      * contents will continue to display normally until the new data is loaded and the contents are replaced.
82015      */
82016     loadingText: 'Loading...',
82017     
82018     /**
82019      * @cfg {String} loadingCls
82020      * The CSS class to apply to the loading message element (defaults to Ext.LoadMask.prototype.msgCls "x-mask-loading")
82021      */
82022     
82023     /**
82024      * @cfg {Boolean} loadingUseMsg
82025      * Whether or not to use the loading message.
82026      * @private
82027      */
82028     loadingUseMsg: true,
82029     
82030
82031     /**
82032      * @cfg {Number} loadingHeight
82033      * If specified, gives an explicit height for the data view when it is showing the {@link #loadingText},
82034      * if that is specified. This is useful to prevent the view's height from collapsing to zero when the
82035      * loading mask is applied and there are no other contents in the data view. Defaults to undefined.
82036      */
82037
82038     /**
82039      * @cfg {String} selectedItemCls
82040      * A CSS class to apply to each selected item in the view (defaults to 'x-view-selected').
82041      */
82042     selectedItemCls: Ext.baseCSSPrefix + 'item-selected',
82043
82044     /**
82045      * @cfg {String} emptyText
82046      * The text to display in the view when there is no data to display (defaults to '').
82047      * Note that when using local data the emptyText will not be displayed unless you set
82048      * the {@link #deferEmptyText} option to false.
82049      */
82050     emptyText: "",
82051
82052     /**
82053      * @cfg {Boolean} deferEmptyText True to defer emptyText being applied until the store's first load
82054      */
82055     deferEmptyText: true,
82056
82057     /**
82058      * @cfg {Boolean} trackOver True to enable mouseenter and mouseleave events
82059      */
82060     trackOver: false,
82061
82062     /**
82063      * @cfg {Boolean} blockRefresh Set this to true to ignore datachanged events on the bound store. This is useful if
82064      * you wish to provide custom transition animations via a plugin (defaults to false)
82065      */
82066     blockRefresh: false,
82067
82068     /**
82069      * @cfg {Boolean} disableSelection <p><tt>true</tt> to disable selection within the DataView. Defaults to <tt>false</tt>.
82070      * This configuration will lock the selection model that the DataView uses.</p>
82071      */
82072
82073
82074     //private
82075     last: false,
82076     
82077     triggerEvent: 'itemclick',
82078     triggerCtEvent: 'containerclick',
82079     
82080     addCmpEvents: function() {
82081         
82082     },
82083
82084     // private
82085     initComponent : function(){
82086         var me = this,
82087             isDef = Ext.isDefined,
82088             itemTpl = me.itemTpl,
82089             memberFn = {};
82090             
82091         if (itemTpl) {
82092             if (Ext.isArray(itemTpl)) {
82093                 // string array
82094                 itemTpl = itemTpl.join('');
82095             } else if (Ext.isObject(itemTpl)) {
82096                 // tpl instance
82097                 memberFn = Ext.apply(memberFn, itemTpl.initialConfig);
82098                 itemTpl = itemTpl.html;
82099             }
82100             
82101             if (!me.itemSelector) {
82102                 me.itemSelector = '.' + me.itemCls;
82103             }
82104             
82105             itemTpl = Ext.String.format('<tpl for="."><div class="{0}">{1}</div></tpl>', me.itemCls, itemTpl);
82106             me.tpl = Ext.create('Ext.XTemplate', itemTpl, memberFn);
82107         }
82108
82109         if (!isDef(me.tpl) || !isDef(me.itemSelector)) {
82110             Ext.Error.raise({
82111                 sourceClass: 'Ext.view.View',
82112                 tpl: me.tpl,
82113                 itemSelector: me.itemSelector,
82114                 msg: "DataView requires both tpl and itemSelector configurations to be defined."
82115             });
82116         }
82117
82118         me.callParent();
82119         if(Ext.isString(me.tpl) || Ext.isArray(me.tpl)){
82120             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
82121         }
82122
82123         // backwards compat alias for overClass/selectedClass
82124         // TODO: Consider support for overCls generation Ext.Component config
82125         if (isDef(me.overCls) || isDef(me.overClass)) {
82126             if (Ext.isDefined(Ext.global.console)) {
82127                 Ext.global.console.warn('Ext.view.View: Using the deprecated overCls or overClass configuration. Use overItemCls instead.');
82128             }
82129             me.overItemCls = me.overCls || me.overClass;
82130             delete me.overCls;
82131             delete me.overClass;
82132         }
82133
82134         if (me.overItemCls) {
82135             me.trackOver = true;
82136         }
82137         
82138         if (isDef(me.selectedCls) || isDef(me.selectedClass)) {
82139             if (Ext.isDefined(Ext.global.console)) {
82140                 Ext.global.console.warn('Ext.view.View: Using the deprecated selectedCls or selectedClass configuration. Use selectedItemCls instead.');
82141             }
82142             me.selectedItemCls = me.selectedCls || me.selectedClass;
82143             delete me.selectedCls;
82144             delete me.selectedClass;
82145         }
82146         
82147         me.addEvents(
82148             /**
82149              * @event beforerefresh
82150              * Fires before the view is refreshed
82151              * @param {Ext.view.View} this The DataView object
82152              */
82153             'beforerefresh',
82154             /**
82155              * @event refresh
82156              * Fires when the view is refreshed
82157              * @param {Ext.view.View} this The DataView object
82158              */
82159             'refresh',
82160             /**
82161              * @event itemupdate
82162              * Fires when the node associated with an individual record is updated
82163              * @param {Ext.data.Model} record The model instance
82164              * @param {Number} index The index of the record/node
82165              * @param {HTMLElement} node The node that has just been updated
82166              */
82167             'itemupdate',
82168             /**
82169              * @event itemadd
82170              * Fires when the nodes associated with an recordset have been added to the underlying store
82171              * @param {Array[Ext.data.Model]} records The model instance
82172              * @param {Number} index The index at which the set of record/nodes starts
82173              * @param {Array[HTMLElement]} node The node that has just been updated
82174              */
82175             'itemadd',
82176             /**
82177              * @event itemremove
82178              * Fires when the node associated with an individual record is removed
82179              * @param {Ext.data.Model} record The model instance
82180              * @param {Number} index The index of the record/node
82181              */
82182             'itemremove'
82183         );
82184
82185         me.addCmpEvents();
82186
82187         if (me.store) {
82188             me.store = Ext.data.StoreManager.lookup(me.store);
82189         }
82190         me.all = new Ext.CompositeElementLite();
82191         me.getSelectionModel().bindComponent(me);
82192     },
82193
82194     onRender: function() {
82195         var me = this,
82196             loadingText = me.loadingText,
82197             loadingHeight = me.loadingHeight,
82198             undef;
82199
82200         me.callParent(arguments);
82201         if (loadingText) {
82202             
82203             // Attach the LoadMask to a *Component* so that it can be sensitive to resizing during long loads.
82204             // If this DataView is floating, then mask this DataView.
82205             // Otherwise, mask its owning Container (or this, if there *is* no owning Container).
82206             // LoadMask captures the element upon render.
82207             me.loadMask = Ext.create('Ext.LoadMask', me.floating ? me : me.ownerCt || me, {
82208                 msg: loadingText,
82209                 msgCls: me.loadingCls,
82210                 useMsg: me.loadingUseMsg,
82211                 listeners: {
82212                     beforeshow: function() {
82213                         me.getTargetEl().update('');
82214                         me.getSelectionModel().deselectAll();
82215                         me.all.clear();
82216                         if (loadingHeight) {
82217                             me.setCalculatedSize(undef, loadingHeight);
82218                         }
82219                     },
82220                     hide: function() {
82221                         if (loadingHeight) {
82222                             me.setHeight(me.height);
82223                         }
82224                     }
82225                 }
82226             });
82227         }
82228     },
82229
82230     getSelectionModel: function(){
82231         var me = this,
82232             mode = 'SINGLE';
82233
82234         if (!me.selModel) {
82235             me.selModel = {};
82236         }
82237
82238         if (me.simpleSelect) {
82239             mode = 'SIMPLE';
82240         } else if (me.multiSelect) {
82241             mode = 'MULTI';
82242         }
82243
82244         Ext.applyIf(me.selModel, {
82245             allowDeselect: me.allowDeselect,
82246             mode: mode
82247         });
82248
82249         if (!me.selModel.events) {
82250             me.selModel = Ext.create('Ext.selection.DataViewModel', me.selModel);
82251         }
82252
82253         if (!me.selModel.hasRelaySetup) {
82254             me.relayEvents(me.selModel, ['selectionchange', 'beforeselect', 'select', 'deselect']);
82255             me.selModel.hasRelaySetup = true;
82256         }
82257
82258         // lock the selection model if user
82259         // has disabled selection
82260         if (me.disableSelection) {
82261             me.selModel.locked = true;
82262         }
82263
82264         return me.selModel;
82265     },
82266
82267     /**
82268      * Refreshes the view by reloading the data from the store and re-rendering the template.
82269      */
82270     refresh: function() {
82271         var me = this,
82272             el,
82273             records;
82274             
82275         if (!me.rendered) {
82276             return;
82277         }
82278         
82279         me.fireEvent('beforerefresh', me);
82280         el = me.getTargetEl();
82281         records = me.store.getRange();
82282
82283         el.update('');
82284         if (records.length < 1) {
82285             if (!me.deferEmptyText || me.hasSkippedEmptyText) {
82286                 el.update(me.emptyText);
82287             }
82288             me.all.clear();
82289         } else {
82290             me.tpl.overwrite(el, me.collectData(records, 0));
82291             me.all.fill(Ext.query(me.getItemSelector(), el.dom));
82292             me.updateIndexes(0);
82293         }
82294         
82295         me.selModel.refresh();
82296         me.hasSkippedEmptyText = true;
82297         me.fireEvent('refresh', me);
82298     },
82299
82300     /**
82301      * Function which can be overridden to provide custom formatting for each Record that is used by this
82302      * DataView's {@link #tpl template} to render each node.
82303      * @param {Array/Object} data The raw data object that was used to create the Record.
82304      * @param {Number} recordIndex the index number of the Record being prepared for rendering.
82305      * @param {Record} record The Record being prepared for rendering.
82306      * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
82307      * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
82308      */
82309     prepareData: function(data, index, record) {
82310         if (record) {    
82311             Ext.apply(data, record.getAssociatedData());            
82312         }
82313         return data;
82314     },
82315     
82316     /**
82317      * <p>Function which can be overridden which returns the data object passed to this
82318      * DataView's {@link #tpl template} to render the whole DataView.</p>
82319      * <p>This is usually an Array of data objects, each element of which is processed by an
82320      * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
82321      * data object as an Array. However, <i>named</i> properties may be placed into the data object to
82322      * provide non-repeating data such as headings, totals etc.</p>
82323      * @param {Array} records An Array of {@link Ext.data.Model}s to be rendered into the DataView.
82324      * @param {Number} startIndex the index number of the Record being prepared for rendering.
82325      * @return {Array} An Array of data objects to be processed by a repeating XTemplate. May also
82326      * contain <i>named</i> properties.
82327      */
82328     collectData : function(records, startIndex){
82329         var r = [],
82330             i = 0,
82331             len = records.length;
82332
82333         for(; i < len; i++){
82334             r[r.length] = this.prepareData(records[i].data, startIndex + i, records[i]);
82335         }
82336
82337         return r;
82338     },
82339
82340     // private
82341     bufferRender : function(records, index){
82342         var div = document.createElement('div');
82343         this.tpl.overwrite(div, this.collectData(records, index));
82344         return Ext.query(this.getItemSelector(), div);
82345     },
82346
82347     // private
82348     onUpdate : function(ds, record){
82349         var me = this,
82350             index = me.store.indexOf(record),
82351             original,
82352             node;
82353
82354         if (index > -1){
82355             original = me.all.elements[index];
82356             node = me.bufferRender([record], index)[0];
82357
82358             me.all.replaceElement(index, node, true);
82359             me.updateIndexes(index, index);
82360
82361             // Maintain selection after update
82362             // TODO: Move to approriate event handler.
82363             me.selModel.refresh();
82364             me.fireEvent('itemupdate', record, index, node);
82365         }
82366
82367     },
82368
82369     // private
82370     onAdd : function(ds, records, index) {
82371         var me = this,
82372             nodes;
82373             
82374         if (me.all.getCount() === 0) {
82375             me.refresh();
82376             return;
82377         }
82378         
82379         nodes = me.bufferRender(records, index);
82380         me.doAdd(nodes, records, index);
82381
82382         me.selModel.refresh();
82383         me.updateIndexes(index);
82384         me.fireEvent('itemadd', records, index, nodes);
82385     },
82386
82387     doAdd: function(nodes, records, index) {
82388         var n, a = this.all.elements;
82389         if (index < this.all.getCount()) {
82390             n = this.all.item(index).insertSibling(nodes, 'before', true);
82391             a.splice.apply(a, [index, 0].concat(nodes));
82392         } 
82393         else {
82394             n = this.all.last().insertSibling(nodes, 'after', true);
82395             a.push.apply(a, nodes);
82396         }    
82397     },
82398     
82399     // private
82400     onRemove : function(ds, record, index) {
82401         var me = this;
82402         
82403         me.doRemove(record, index);
82404         me.updateIndexes(index);
82405         if (me.store.getCount() === 0){
82406             me.refresh();
82407         }
82408         me.fireEvent('itemremove', record, index);
82409     },
82410     
82411     doRemove: function(record, index) {
82412         this.all.removeElement(index, true);
82413     },
82414
82415     /**
82416      * Refreshes an individual node's data from the store.
82417      * @param {Number} index The item's data index in the store
82418      */
82419     refreshNode : function(index){
82420         this.onUpdate(this.store, this.store.getAt(index));
82421     },
82422
82423     // private
82424     updateIndexes : function(startIndex, endIndex) {
82425         var ns = this.all.elements,
82426             records = this.store.getRange();
82427         startIndex = startIndex || 0;
82428         endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
82429         for(var i = startIndex; i <= endIndex; i++){
82430             ns[i].viewIndex = i;
82431             ns[i].viewRecordId = records[i].internalId;
82432             if (!ns[i].boundView) {
82433                 ns[i].boundView = this.id;
82434             }
82435         }
82436     },
82437
82438     /**
82439      * Returns the store associated with this DataView.
82440      * @return {Ext.data.Store} The store
82441      */
82442     getStore : function(){
82443         return this.store;
82444     },
82445
82446     /**
82447      * Changes the data store bound to this view and refreshes it.
82448      * @param {Store} store The store to bind to this view
82449      */
82450     bindStore : function(store, initial) {
82451         var me = this;
82452         
82453         if (!initial && me.store) {
82454             if (store !== me.store && me.store.autoDestroy) {
82455                 me.store.destroy();
82456             } 
82457             else {
82458                 me.mun(me.store, {
82459                     scope: me,
82460                     datachanged: me.onDataChanged,
82461                     add: me.onAdd,
82462                     remove: me.onRemove,
82463                     update: me.onUpdate,
82464                     clear: me.refresh
82465                 });
82466             }
82467             if (!store) {
82468                 if (me.loadMask) {
82469                     me.loadMask.bindStore(null);
82470                 }
82471                 me.store = null;
82472             }
82473         }
82474         if (store) {
82475             store = Ext.data.StoreManager.lookup(store);
82476             me.mon(store, {
82477                 scope: me,
82478                 datachanged: me.onDataChanged,
82479                 add: me.onAdd,
82480                 remove: me.onRemove,
82481                 update: me.onUpdate,
82482                 clear: me.refresh
82483             });
82484             if (me.loadMask) {
82485                 me.loadMask.bindStore(store);
82486             }
82487         }
82488         
82489         me.store = store;
82490         // Bind the store to our selection model
82491         me.getSelectionModel().bind(store);
82492         
82493         if (store) {
82494             me.refresh(true);
82495         }
82496     },
82497
82498     /**
82499      * @private
82500      * Calls this.refresh if this.blockRefresh is not true
82501      */
82502     onDataChanged: function() {
82503         if (this.blockRefresh !== true) {
82504             this.refresh.apply(this, arguments);
82505         }
82506     },
82507
82508     /**
82509      * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
82510      * @param {HTMLElement} node
82511      * @return {HTMLElement} The template node
82512      */
82513     findItemByChild: function(node){
82514         return Ext.fly(node).findParent(this.getItemSelector(), this.getTargetEl());
82515     },
82516     
82517     /**
82518      * Returns the template node by the Ext.EventObject or null if it is not found.
82519      * @param {Ext.EventObject} e
82520      */
82521     findTargetByEvent: function(e) {
82522         return e.getTarget(this.getItemSelector(), this.getTargetEl());
82523     },
82524
82525
82526     /**
82527      * Gets the currently selected nodes.
82528      * @return {Array} An array of HTMLElements
82529      */
82530     getSelectedNodes: function(){
82531         var nodes   = [],
82532             records = this.selModel.getSelection(),
82533             ln = records.length,
82534             i  = 0;
82535
82536         for (; i < ln; i++) {
82537             nodes.push(this.getNode(records[i]));
82538         }
82539
82540         return nodes;
82541     },
82542
82543     /**
82544      * Gets an array of the records from an array of nodes
82545      * @param {Array} nodes The nodes to evaluate
82546      * @return {Array} records The {@link Ext.data.Model} objects
82547      */
82548     getRecords: function(nodes) {
82549         var records = [],
82550             i = 0,
82551             len = nodes.length,
82552             data = this.store.data;
82553
82554         for (; i < len; i++) {
82555             records[records.length] = data.getByKey(nodes[i].viewRecordId);
82556         }
82557
82558         return records;
82559     },
82560
82561     /**
82562      * Gets a record from a node
82563      * @param {Element/HTMLElement} node The node to evaluate
82564      * 
82565      * @return {Record} record The {@link Ext.data.Model} object
82566      */
82567     getRecord: function(node){
82568         return this.store.data.getByKey(Ext.getDom(node).viewRecordId);
82569     },
82570     
82571
82572     /**
82573      * Returns true if the passed node is selected, else false.
82574      * @param {HTMLElement/Number/Ext.data.Model} node The node, node index or record to check
82575      * @return {Boolean} True if selected, else false
82576      */
82577     isSelected : function(node) {
82578         // TODO: El/Idx/Record
82579         var r = this.getRecord(node);
82580         return this.selModel.isSelected(r);
82581     },
82582     
82583     /**
82584      * Selects a record instance by record instance or index.
82585      * @param {Ext.data.Model/Index} records An array of records or an index
82586      * @param {Boolean} keepExisting
82587      * @param {Boolean} suppressEvent Set to false to not fire a select event
82588      */
82589     select: function(records, keepExisting, suppressEvent) {
82590         this.selModel.select(records, keepExisting, suppressEvent);
82591     },
82592
82593     /**
82594      * Deselects a record instance by record instance or index.
82595      * @param {Ext.data.Model/Index} records An array of records or an index
82596      * @param {Boolean} suppressEvent Set to false to not fire a deselect event
82597      */
82598     deselect: function(records, suppressEvent) {
82599         this.selModel.deselect(records, suppressEvent);
82600     },
82601
82602     /**
82603      * Gets a template node.
82604      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node,
82605      * the id of a template node or the record associated with the node.
82606      * @return {HTMLElement} The node or null if it wasn't found
82607      */
82608     getNode : function(nodeInfo) {
82609         if (Ext.isString(nodeInfo)) {
82610             return document.getElementById(nodeInfo);
82611         } else if (Ext.isNumber(nodeInfo)) {
82612             return this.all.elements[nodeInfo];
82613         } else if (nodeInfo instanceof Ext.data.Model) {
82614             return this.getNodeByRecord(nodeInfo);
82615         }
82616         return nodeInfo;
82617     },
82618     
82619     /**
82620      * @private
82621      */
82622     getNodeByRecord: function(record) {
82623         var ns = this.all.elements,
82624             ln = ns.length,
82625             i = 0;
82626         
82627         for (; i < ln; i++) {
82628             if (ns[i].viewRecordId === record.internalId) {
82629                 return ns[i];
82630             }
82631         }
82632         
82633         return null;
82634     },
82635     
82636     /**
82637      * Gets a range nodes.
82638      * @param {Number} start (optional) The index of the first node in the range
82639      * @param {Number} end (optional) The index of the last node in the range
82640      * @return {Array} An array of nodes
82641      */
82642     getNodes: function(start, end) {
82643         var ns = this.all.elements,
82644             nodes = [],
82645             i;
82646
82647         start = start || 0;
82648         end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
82649         if (start <= end) {
82650             for (i = start; i <= end && ns[i]; i++) {
82651                 nodes.push(ns[i]);
82652             }
82653         } else {
82654             for (i = start; i >= end && ns[i]; i--) {
82655                 nodes.push(ns[i]);
82656             }
82657         }
82658         return nodes;
82659     },
82660
82661     /**
82662      * Finds the index of the passed node.
82663      * @param {HTMLElement/String/Number/Record} nodeInfo An HTMLElement template node, index of a template node, the id of a template node
82664      * or a record associated with a node.
82665      * @return {Number} The index of the node or -1
82666      */
82667     indexOf: function(node) {
82668         node = this.getNode(node);
82669         if (Ext.isNumber(node.viewIndex)) {
82670             return node.viewIndex;
82671         }
82672         return this.all.indexOf(node);
82673     },
82674
82675     onDestroy : function() {
82676         var me = this;
82677         
82678         me.all.clear();
82679         me.callParent();
82680         me.bindStore(null);
82681         me.selModel.destroy();
82682     },
82683
82684     // invoked by the selection model to maintain visual UI cues
82685     onItemSelect: function(record) {
82686         var node = this.getNode(record);
82687         Ext.fly(node).addCls(this.selectedItemCls);
82688     },
82689
82690     // invoked by the selection model to maintain visual UI cues
82691     onItemDeselect: function(record) {
82692         var node = this.getNode(record);
82693         Ext.fly(node).removeCls(this.selectedItemCls);
82694     },
82695     
82696     getItemSelector: function() {
82697         return this.itemSelector;
82698     }
82699 }, function() {
82700     // all of this information is available directly
82701     // from the SelectionModel itself, the only added methods
82702     // to DataView regarding selection will perform some transformation/lookup
82703     // between HTMLElement/Nodes to records and vice versa.
82704     Ext.deprecate('extjs', '4.0', function() {
82705         Ext.view.AbstractView.override({
82706             /**
82707              * @cfg {Boolean} multiSelect
82708              * True to allow selection of more than one item at a time, false to allow selection of only a single item
82709              * at a time or no selection at all, depending on the value of {@link #singleSelect} (defaults to false).
82710              */
82711             /**
82712              * @cfg {Boolean} singleSelect
82713              * True to allow selection of exactly one item at a time, false to allow no selection at all (defaults to false).
82714              * Note that if {@link #multiSelect} = true, this value will be ignored.
82715              */
82716             /**
82717              * @cfg {Boolean} simpleSelect
82718              * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
82719              * false to force the user to hold Ctrl or Shift to select more than on item (defaults to false).
82720              */
82721             
82722             /**
82723              * Gets the number of selected nodes.
82724              * @return {Number} The node count
82725              */
82726             getSelectionCount : function(){
82727                 if (Ext.global.console) {
82728                     Ext.global.console.warn("DataView: getSelectionCount will be removed, please interact with the Ext.selection.DataViewModel");
82729                 }
82730                 return this.selModel.getSelection().length;
82731             },
82732         
82733             /**
82734              * Gets an array of the selected records
82735              * @return {Array} An array of {@link Ext.data.Model} objects
82736              */
82737             getSelectedRecords : function(){
82738                 if (Ext.global.console) {
82739                     Ext.global.console.warn("DataView: getSelectedRecords will be removed, please interact with the Ext.selection.DataViewModel");
82740                 }
82741                 return this.selModel.getSelection();
82742             },
82743     
82744             select: function(records, keepExisting, supressEvents) {
82745                 if (Ext.global.console) {
82746                     Ext.global.console.warn("DataView: select will be removed, please access select through a DataView's SelectionModel, ie: view.getSelectionModel().select()");
82747                 }
82748                 var sm = this.getSelectionModel();
82749                 return sm.select.apply(sm, arguments);
82750             },
82751             
82752             clearSelections: function() {
82753                 if (Ext.global.console) {
82754                     Ext.global.console.warn("DataView: clearSelections will be removed, please access deselectAll through DataView's SelectionModel, ie: view.getSelectionModel().deselectAll()");
82755                 }
82756                 var sm = this.getSelectionModel();
82757                 return sm.deselectAll();
82758             }
82759         });    
82760     });
82761 });
82762
82763 /**
82764  * @class Ext.Action
82765  * <p>An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
82766  * can be usefully shared among multiple components.  Actions let you share handlers, configuration options and UI
82767  * updates across any components that support the Action interface (primarily {@link Ext.toolbar.Toolbar}, {@link Ext.button.Button}
82768  * and {@link Ext.menu.Menu} components).</p>
82769  * <p>Use a single Action instance as the config object for any number of UI Components which share the same configuration. The
82770  * Action not only supplies the configuration, but allows all Components based upon it to have a common set of methods
82771  * called at once through a single call to the Action.</p>
82772  * <p>Any Component that is to be configured with an Action must also support
82773  * the following methods:<ul>
82774  * <li><code>setText(string)</code></li>
82775  * <li><code>setIconCls(string)</code></li>
82776  * <li><code>setDisabled(boolean)</code></li>
82777  * <li><code>setVisible(boolean)</code></li>
82778  * <li><code>setHandler(function)</code></li></ul>.</p>
82779  * <p>This allows the Action to control its associated Components.</p>
82780  * Example usage:<br>
82781  * <pre><code>
82782 // Define the shared Action.  Each Component below will have the same
82783 // display text and icon, and will display the same message on click.
82784 var action = new Ext.Action({
82785     {@link #text}: 'Do something',
82786     {@link #handler}: function(){
82787         Ext.Msg.alert('Click', 'You did something.');
82788     },
82789     {@link #iconCls}: 'do-something',
82790     {@link #itemId}: 'myAction'
82791 });
82792
82793 var panel = new Ext.panel.Panel({
82794     title: 'Actions',
82795     width: 500,
82796     height: 300,
82797     tbar: [
82798         // Add the Action directly to a toolbar as a menu button
82799         action,
82800         {
82801             text: 'Action Menu',
82802             // Add the Action to a menu as a text item
82803             menu: [action]
82804         }
82805     ],
82806     items: [
82807         // Add the Action to the panel body as a standard button
82808         new Ext.button.Button(action)
82809     ],
82810     renderTo: Ext.getBody()
82811 });
82812
82813 // Change the text for all components using the Action
82814 action.setText('Something else');
82815
82816 // Reference an Action through a container using the itemId
82817 var btn = panel.getComponent('myAction');
82818 var aRef = btn.baseAction;
82819 aRef.setText('New text');
82820 </code></pre>
82821  * @constructor
82822  * @param {Object} config The configuration options
82823  */
82824 Ext.define('Ext.Action', {
82825
82826     /* Begin Definitions */
82827
82828     /* End Definitions */
82829
82830     /**
82831      * @cfg {String} text The text to set for all components configured by this Action (defaults to '').
82832      */
82833     /**
82834      * @cfg {String} iconCls
82835      * The CSS class selector that specifies a background image to be used as the header icon for
82836      * all components configured by this Action (defaults to '').
82837      * <p>An example of specifying a custom icon class would be something like:
82838      * </p><pre><code>
82839 // specify the property in the config for the class:
82840      ...
82841      iconCls: 'do-something'
82842
82843 // css class that specifies background image to be used as the icon image:
82844 .do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
82845 </code></pre>
82846      */
82847     /**
82848      * @cfg {Boolean} disabled True to disable all components configured by this Action, false to enable them (defaults to false).
82849      */
82850     /**
82851      * @cfg {Boolean} hidden True to hide all components configured by this Action, false to show them (defaults to false).
82852      */
82853     /**
82854      * @cfg {Function} handler The function that will be invoked by each component tied to this Action
82855      * when the component's primary event is triggered (defaults to undefined).
82856      */
82857     /**
82858      * @cfg {String} itemId
82859      * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
82860      */
82861     /**
82862      * @cfg {Object} scope The scope (<code><b>this</b></code> reference) in which the
82863      * <code>{@link #handler}</code> is executed. Defaults to the browser window.
82864      */
82865
82866     constructor : function(config){
82867         this.initialConfig = config;
82868         this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
82869         this.items = [];
82870     },
82871
82872     // private
82873     isAction : true,
82874
82875     /**
82876      * Sets the text to be displayed by all components configured by this Action.
82877      * @param {String} text The text to display
82878      */
82879     setText : function(text){
82880         this.initialConfig.text = text;
82881         this.callEach('setText', [text]);
82882     },
82883
82884     /**
82885      * Gets the text currently displayed by all components configured by this Action.
82886      */
82887     getText : function(){
82888         return this.initialConfig.text;
82889     },
82890
82891     /**
82892      * Sets the icon CSS class for all components configured by this Action.  The class should supply
82893      * a background image that will be used as the icon image.
82894      * @param {String} cls The CSS class supplying the icon image
82895      */
82896     setIconCls : function(cls){
82897         this.initialConfig.iconCls = cls;
82898         this.callEach('setIconCls', [cls]);
82899     },
82900
82901     /**
82902      * Gets the icon CSS class currently used by all components configured by this Action.
82903      */
82904     getIconCls : function(){
82905         return this.initialConfig.iconCls;
82906     },
82907
82908     /**
82909      * Sets the disabled state of all components configured by this Action.  Shortcut method
82910      * for {@link #enable} and {@link #disable}.
82911      * @param {Boolean} disabled True to disable the component, false to enable it
82912      */
82913     setDisabled : function(v){
82914         this.initialConfig.disabled = v;
82915         this.callEach('setDisabled', [v]);
82916     },
82917
82918     /**
82919      * Enables all components configured by this Action.
82920      */
82921     enable : function(){
82922         this.setDisabled(false);
82923     },
82924
82925     /**
82926      * Disables all components configured by this Action.
82927      */
82928     disable : function(){
82929         this.setDisabled(true);
82930     },
82931
82932     /**
82933      * Returns true if the components using this Action are currently disabled, else returns false.  
82934      */
82935     isDisabled : function(){
82936         return this.initialConfig.disabled;
82937     },
82938
82939     /**
82940      * Sets the hidden state of all components configured by this Action.  Shortcut method
82941      * for <code>{@link #hide}</code> and <code>{@link #show}</code>.
82942      * @param {Boolean} hidden True to hide the component, false to show it
82943      */
82944     setHidden : function(v){
82945         this.initialConfig.hidden = v;
82946         this.callEach('setVisible', [!v]);
82947     },
82948
82949     /**
82950      * Shows all components configured by this Action.
82951      */
82952     show : function(){
82953         this.setHidden(false);
82954     },
82955
82956     /**
82957      * Hides all components configured by this Action.
82958      */
82959     hide : function(){
82960         this.setHidden(true);
82961     },
82962
82963     /**
82964      * Returns true if the components configured by this Action are currently hidden, else returns false.
82965      */
82966     isHidden : function(){
82967         return this.initialConfig.hidden;
82968     },
82969
82970     /**
82971      * Sets the function that will be called by each Component using this action when its primary event is triggered.
82972      * @param {Function} fn The function that will be invoked by the action's components.  The function
82973      * will be called with no arguments.
82974      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component firing the event.
82975      */
82976     setHandler : function(fn, scope){
82977         this.initialConfig.handler = fn;
82978         this.initialConfig.scope = scope;
82979         this.callEach('setHandler', [fn, scope]);
82980     },
82981
82982     /**
82983      * Executes the specified function once for each Component currently tied to this Action.  The function passed
82984      * in should accept a single argument that will be an object that supports the basic Action config/method interface.
82985      * @param {Function} fn The function to execute for each component
82986      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed.  Defaults to the Component.
82987      */
82988     each : function(fn, scope){
82989         Ext.each(this.items, fn, scope);
82990     },
82991
82992     // private
82993     callEach : function(fnName, args){
82994         var items = this.items,
82995             i = 0,
82996             len = items.length;
82997             
82998         for(; i < len; i++){
82999             items[i][fnName].apply(items[i], args);
83000         }
83001     },
83002
83003     // private
83004     addComponent : function(comp){
83005         this.items.push(comp);
83006         comp.on('destroy', this.removeComponent, this);
83007     },
83008
83009     // private
83010     removeComponent : function(comp){
83011         Ext.Array.remove(this.items, comp);
83012     },
83013
83014     /**
83015      * Executes this Action manually using the handler function specified in the original config object
83016      * or the handler function set with <code>{@link #setHandler}</code>.  Any arguments passed to this
83017      * function will be passed on to the handler function.
83018      * @param {Mixed} arg1 (optional) Variable number of arguments passed to the handler function
83019      * @param {Mixed} arg2 (optional)
83020      * @param {Mixed} etc... (optional)
83021      */
83022     execute : function(){
83023         this.initialConfig.handler.apply(this.initialConfig.scope || Ext.global, arguments);
83024     }
83025 });
83026
83027 /**
83028  * Component layout for editors
83029  * @class Ext.layout.component.Editor
83030  * @extends Ext.layout.component.Component
83031  * @private
83032  */
83033 Ext.define('Ext.layout.component.Editor', {
83034
83035     /* Begin Definitions */
83036
83037     alias: ['layout.editor'],
83038
83039     extend: 'Ext.layout.component.Component',
83040
83041     /* End Definitions */
83042
83043     onLayout: function(width, height) {
83044         var me = this,
83045             owner = me.owner,
83046             autoSize = owner.autoSize;
83047             
83048         if (autoSize === true) {
83049             autoSize = {
83050                 width: 'field',
83051                 height: 'field'    
83052             };
83053         }
83054         
83055         if (autoSize) {
83056             width = me.getDimension(owner, autoSize.width, 'Width', width);
83057             height = me.getDimension(owner, autoSize.height, 'Height', height);
83058         }
83059         me.setTargetSize(width, height);
83060         owner.field.setSize(width, height);
83061     },
83062     
83063     getDimension: function(owner, type, dimension, actual){
83064         var method = 'get' + dimension;
83065         switch (type) {
83066             case 'boundEl':
83067                 return owner.boundEl[method]();
83068             case 'field':
83069                 return owner.field[method]();
83070             default:
83071                 return actual;
83072         }
83073     }
83074 });
83075 /**
83076  * @class Ext.Editor
83077  * @extends Ext.Component
83078  *
83079  * <p>
83080  * The Editor class is used to provide inline editing for elements on the page. The editor
83081  * is backed by a {@link Ext.form.field.Field} that will be displayed to edit the underlying content.
83082  * The editor is a floating Component, when the editor is shown it is automatically aligned to
83083  * display over the top of the bound element it is editing. The Editor contains several options
83084  * for how to handle key presses:
83085  * <ul>
83086  * <li>{@link #completeOnEnter}</li>
83087  * <li>{@link #cancelOnEsc}</li>
83088  * <li>{@link #swallowKeys}</li>
83089  * </ul>
83090  * It also has options for how to use the value once the editor has been activated:
83091  * <ul>
83092  * <li>{@link #revertInvalid}</li>
83093  * <li>{@link #ignoreNoChange}</li>
83094  * <li>{@link #updateEl}</li>
83095  * </ul>
83096  * Sample usage:
83097  * </p>
83098  * <pre><code>
83099 var editor = new Ext.Editor({
83100     updateEl: true, // update the innerHTML of the bound element when editing completes
83101     field: {
83102         xtype: 'textfield'
83103     }
83104 });
83105 var el = Ext.get('my-text'); // The element to 'edit'
83106 editor.startEdit(el); // The value of the field will be taken as the innerHTML of the element.
83107  * </code></pre>
83108  * {@img Ext.Editor/Ext.Editor.png Ext.Editor component}
83109  *
83110  * @constructor
83111  * Create a new Editor
83112  * @param {Object} config The config object
83113  * @xtype editor
83114  */
83115 Ext.define('Ext.Editor', {
83116
83117     /* Begin Definitions */
83118
83119     extend: 'Ext.Component',
83120
83121     alias: 'widget.editor',
83122
83123     requires: ['Ext.layout.component.Editor'],
83124
83125     /* End Definitions */
83126
83127    componentLayout: 'editor',
83128
83129     /**
83130     * @cfg {Ext.form.field.Field} field
83131     * The Field object (or descendant) or config object for field
83132     */
83133
83134     /**
83135      * @cfg {Boolean} allowBlur
83136      * True to {@link #completeEdit complete the editing process} if in edit mode when the
83137      * field is blurred. Defaults to <tt>true</tt>.
83138      */
83139     allowBlur: true,
83140
83141     /**
83142      * @cfg {Boolean/Object} autoSize
83143      * True for the editor to automatically adopt the size of the underlying field. Otherwise, an object
83144      * can be passed to indicate where to get each dimension. The available properties are 'boundEl' and
83145      * 'field'. If a dimension is not specified, it will use the underlying height/width specified on
83146      * the editor object.
83147      * Examples:
83148      * <pre><code>
83149 autoSize: true // The editor will be sized to the height/width of the field
83150
83151 height: 21,
83152 autoSize: {
83153     width: 'boundEl' // The width will be determined by the width of the boundEl, the height from the editor (21)
83154 }
83155
83156 autoSize: {
83157     width: 'field', // Width from the field
83158     height: 'boundEl' // Height from the boundEl
83159 }
83160      * </pre></code>
83161      */
83162
83163     /**
83164      * @cfg {Boolean} revertInvalid
83165      * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
83166      * validation fails (defaults to true)
83167      */
83168     revertInvalid: true,
83169
83170     /**
83171      * @cfg {Boolean} ignoreNoChange
83172      * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
83173      * the value has not changed (defaults to false).  Applies only to string values - edits for other data types
83174      * will never be ignored.
83175      */
83176
83177     /**
83178      * @cfg {Boolean} hideEl
83179      * False to keep the bound element visible while the editor is displayed (defaults to true)
83180      */
83181
83182     /**
83183      * @cfg {Mixed} value
83184      * The data value of the underlying field (defaults to "")
83185      */
83186     value : '',
83187
83188     /**
83189      * @cfg {String} alignment
83190      * The position to align to (see {@link Ext.core.Element#alignTo} for more details, defaults to "c-c?").
83191      */
83192     alignment: 'c-c?',
83193
83194     /**
83195      * @cfg {Array} offsets
83196      * The offsets to use when aligning (see {@link Ext.core.Element#alignTo} for more details. Defaults to <tt>[0, 0]</tt>.
83197      */
83198     offsets: [0, 0],
83199
83200     /**
83201      * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop"
83202      * for bottom-right shadow (defaults to "frame")
83203      */
83204     shadow : 'frame',
83205
83206     /**
83207      * @cfg {Boolean} constrain True to constrain the editor to the viewport
83208      */
83209     constrain : false,
83210
83211     /**
83212      * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true)
83213      */
83214     swallowKeys : true,
83215
83216     /**
83217      * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed. Defaults to <tt>true</tt>.
83218      */
83219     completeOnEnter : true,
83220
83221     /**
83222      * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed. Defaults to <tt>true</tt>.
83223      */
83224     cancelOnEsc : true,
83225
83226     /**
83227      * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false)
83228      */
83229     updateEl : false,
83230
83231     /**
83232      * @cfg {Mixed} parentEl An element to render to. Defaults to the <tt>document.body</tt>.
83233      */
83234
83235     // private overrides
83236     hidden: true,
83237     baseCls: Ext.baseCSSPrefix + 'editor',
83238
83239     initComponent : function() {
83240         var me = this,
83241             field = me.field = Ext.ComponentManager.create(me.field, 'textfield');
83242
83243         Ext.apply(field, {
83244             inEditor: true,
83245             msgTarget: field.msgTarget == 'title' ? 'title' :  'qtip'
83246         });
83247         me.mon(field, {
83248             scope: me,
83249             blur: {
83250                 fn: me.onBlur,
83251                 // slight delay to avoid race condition with startEdits (e.g. grid view refresh)
83252                 delay: 1
83253             },
83254             specialkey: me.onSpecialKey
83255         });
83256
83257         if (field.grow) {
83258             me.mon(field, 'autosize', me.onAutoSize,  me, {delay: 1});
83259         }
83260         me.floating = {
83261             constrain: me.constrain
83262         };
83263
83264         me.callParent(arguments);
83265
83266         me.addEvents(
83267             /**
83268              * @event beforestartedit
83269              * Fires when editing is initiated, but before the value changes.  Editing can be canceled by returning
83270              * false from the handler of this event.
83271              * @param {Ext.Editor} this
83272              * @param {Ext.core.Element} boundEl The underlying element bound to this editor
83273              * @param {Mixed} value The field value being set
83274              */
83275             'beforestartedit',
83276             /**
83277              * @event startedit
83278              * Fires when this editor is displayed
83279              * @param {Ext.Editor} this
83280              * @param {Ext.core.Element} boundEl The underlying element bound to this editor
83281              * @param {Mixed} value The starting field value
83282              */
83283             'startedit',
83284             /**
83285              * @event beforecomplete
83286              * Fires after a change has been made to the field, but before the change is reflected in the underlying
83287              * field.  Saving the change to the field can be canceled by returning false from the handler of this event.
83288              * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
83289              * event will not fire since no edit actually occurred.
83290              * @param {Editor} this
83291              * @param {Mixed} value The current field value
83292              * @param {Mixed} startValue The original field value
83293              */
83294             'beforecomplete',
83295             /**
83296              * @event complete
83297              * Fires after editing is complete and any changed value has been written to the underlying field.
83298              * @param {Ext.Editor} this
83299              * @param {Mixed} value The current field value
83300              * @param {Mixed} startValue The original field value
83301              */
83302             'complete',
83303             /**
83304              * @event canceledit
83305              * Fires after editing has been canceled and the editor's value has been reset.
83306              * @param {Ext.Editor} this
83307              * @param {Mixed} value The user-entered field value that was discarded
83308              * @param {Mixed} startValue The original field value that was set back into the editor after cancel
83309              */
83310             'canceledit',
83311             /**
83312              * @event specialkey
83313              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check
83314              * {@link Ext.EventObject#getKey} to determine which key was pressed.
83315              * @param {Ext.Editor} this
83316              * @param {Ext.form.field.Field} The field attached to this editor
83317              * @param {Ext.EventObject} event The event object
83318              */
83319             'specialkey'
83320         );
83321     },
83322
83323     // private
83324     onAutoSize: function(){
83325         this.doComponentLayout();
83326     },
83327
83328     // private
83329     onRender : function(ct, position) {
83330         var me = this,
83331             field = me.field;
83332
83333         me.callParent(arguments);
83334
83335         field.render(me.el);
83336         //field.hide();
83337         // Ensure the field doesn't get submitted as part of any form
83338         field.inputEl.dom.name = '';
83339         if (me.swallowKeys) {
83340             field.inputEl.swallowEvent([
83341                 'keypress', // *** Opera
83342                 'keydown'   // *** all other browsers
83343             ]);
83344         }
83345     },
83346
83347     // private
83348     onSpecialKey : function(field, event) {
83349         var me = this,
83350             key = event.getKey(),
83351             complete = me.completeOnEnter && key == event.ENTER,
83352             cancel = me.cancelOnEsc && key == event.ESC;
83353
83354         if (complete || cancel) {
83355             event.stopEvent();
83356             // Must defer this slightly to prevent exiting edit mode before the field's own
83357             // key nav can handle the enter key, e.g. selecting an item in a combobox list
83358             Ext.defer(function() {
83359                 if (complete) {
83360                     me.completeEdit();
83361                 } else {
83362                     me.cancelEdit();
83363                 }
83364                 if (field.triggerBlur) {
83365                     field.triggerBlur();
83366                 }
83367             }, 10);
83368         }
83369
83370         this.fireEvent('specialkey', this, field, event);
83371     },
83372
83373     /**
83374      * Starts the editing process and shows the editor.
83375      * @param {Mixed} el The element to edit
83376      * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
83377       * to the innerHTML of el.
83378      */
83379     startEdit : function(el, value) {
83380         var me = this,
83381             field = me.field;
83382
83383         me.completeEdit();
83384         me.boundEl = Ext.get(el);
83385         value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
83386
83387         if (!me.rendered) {
83388             me.render(me.parentEl || document.body);
83389         }
83390
83391         if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
83392             me.startValue = value;
83393             me.show();
83394             field.reset();
83395             field.setValue(value);
83396             me.realign(true);
83397             field.focus(false, 10);
83398             if (field.autoSize) {
83399                 field.autoSize();
83400             }
83401             me.editing = true;
83402         }
83403     },
83404
83405     /**
83406      * Realigns the editor to the bound field based on the current alignment config value.
83407      * @param {Boolean} autoSize (optional) True to size the field to the dimensions of the bound element.
83408      */
83409     realign : function(autoSize) {
83410         var me = this;
83411         if (autoSize === true) {
83412             me.doComponentLayout();
83413         }
83414         me.alignTo(me.boundEl, me.alignment, me.offsets);
83415     },
83416
83417     /**
83418      * Ends the editing process, persists the changed value to the underlying field, and hides the editor.
83419      * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after edit (defaults to false)
83420      */
83421     completeEdit : function(remainVisible) {
83422         var me = this,
83423             field = me.field,
83424             value;
83425
83426         if (!me.editing) {
83427             return;
83428         }
83429
83430         // Assert combo values first
83431         if (field.assertValue) {
83432             field.assertValue();
83433         }
83434
83435         value = me.getValue();
83436         if (!field.isValid()) {
83437             if (me.revertInvalid !== false) {
83438                 me.cancelEdit(remainVisible);
83439             }
83440             return;
83441         }
83442
83443         if (String(value) === String(me.startValue) && me.ignoreNoChange) {
83444             me.hideEdit(remainVisible);
83445             return;
83446         }
83447
83448         if (me.fireEvent('beforecomplete', me, value, me.startValue) !== false) {
83449             // Grab the value again, may have changed in beforecomplete
83450             value = me.getValue();
83451             if (me.updateEl && me.boundEl) {
83452                 me.boundEl.update(value);
83453             }
83454             me.hideEdit(remainVisible);
83455             me.fireEvent('complete', me, value, me.startValue);
83456         }
83457     },
83458
83459     // private
83460     onShow : function() {
83461         var me = this;
83462
83463         me.callParent(arguments);
83464         if (me.hideEl !== false) {
83465             me.boundEl.hide();
83466         }
83467         me.fireEvent("startedit", me.boundEl, me.startValue);
83468     },
83469
83470     /**
83471      * Cancels the editing process and hides the editor without persisting any changes.  The field value will be
83472      * reverted to the original starting value.
83473      * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after
83474      * cancel (defaults to false)
83475      */
83476     cancelEdit : function(remainVisible) {
83477         var me = this,
83478             startValue = me.startValue,
83479             value;
83480
83481         if (me.editing) {
83482             value = me.getValue();
83483             me.setValue(startValue);
83484             me.hideEdit(remainVisible);
83485             me.fireEvent('canceledit', me, value, startValue);
83486         }
83487     },
83488
83489     // private
83490     hideEdit: function(remainVisible) {
83491         if (remainVisible !== true) {
83492             this.editing = false;
83493             this.hide();
83494         }
83495     },
83496
83497     // private
83498     onBlur : function() {
83499         var me = this;
83500
83501         // selectSameEditor flag allows the same editor to be started without onBlur firing on itself
83502         if(me.allowBlur === true && me.editing && me.selectSameEditor !== true) {
83503             me.completeEdit();
83504         }
83505     },
83506
83507     // private
83508     onHide : function() {
83509         var me = this,
83510             field = me.field;
83511
83512         if (me.editing) {
83513             me.completeEdit();
83514             return;
83515         }
83516         field.blur();
83517         if (field.collapse) {
83518             field.collapse();
83519         }
83520
83521         //field.hide();
83522         if (me.hideEl !== false) {
83523             me.boundEl.show();
83524         }
83525         me.callParent(arguments);
83526     },
83527
83528     /**
83529      * Sets the data value of the editor
83530      * @param {Mixed} value Any valid value supported by the underlying field
83531      */
83532     setValue : function(value) {
83533         this.field.setValue(value);
83534     },
83535
83536     /**
83537      * Gets the data value of the editor
83538      * @return {Mixed} The data value
83539      */
83540     getValue : function() {
83541         return this.field.getValue();
83542     },
83543
83544     beforeDestroy : function() {
83545         var me = this;
83546
83547         Ext.destroy(me.field);
83548         delete me.field;
83549         delete me.parentEl;
83550         delete me.boundEl;
83551
83552         me.callParent(arguments);
83553     }
83554 });
83555 /**
83556  * @class Ext.Img
83557  * @extends Ext.Component
83558  *
83559  * Simple helper class for easily creating image components. This simply renders an image tag to the DOM
83560  * with the configured src.
83561  *
83562  * {@img Ext.Img/Ext.Img.png Ext.Img component}
83563  *
83564  * ## Example usage: 
83565  *
83566  *     var changingImage = Ext.create('Ext.Img', {
83567  *         src: 'http://www.sencha.com/img/20110215-feat-html5.png',
83568  *         renderTo: Ext.getBody()
83569  *     });
83570  *      
83571  *     // change the src of the image programmatically
83572  *     changingImage.setSrc('http://www.sencha.com/img/20110215-feat-perf.png');
83573 */
83574 Ext.define('Ext.Img', {
83575     extend: 'Ext.Component',
83576     alias: ['widget.image', 'widget.imagecomponent'],
83577     /** @cfg {String} src The image src */
83578     src: '',
83579
83580     getElConfig: function() {
83581         return {
83582             tag: 'img',
83583             src: this.src
83584         };
83585     },
83586     
83587     // null out this function, we can't set any html inside the image
83588     initRenderTpl: Ext.emptyFn,
83589     
83590     /**
83591      * Updates the {@link #src} of the image
83592      */
83593     setSrc: function(src) {
83594         var me = this,
83595             img = me.el;
83596         me.src = src;
83597         if (img) {
83598             img.dom.src = src;
83599         }
83600     }
83601 });
83602
83603 /**
83604  * @class Ext.Layer
83605  * @extends Ext.core.Element
83606  * An extended {@link Ext.core.Element} object that supports a shadow and shim, constrain to viewport and
83607  * automatic maintaining of shadow/shim positions.
83608  * @cfg {Boolean} shim False to disable the iframe shim in browsers which need one (defaults to true)
83609  * @cfg {String/Boolean} shadow True to automatically create an {@link Ext.Shadow}, or a string indicating the
83610  * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow. (defaults to false)
83611  * @cfg {Object} dh DomHelper object config to create element with (defaults to {tag: 'div', cls: 'x-layer'}).
83612  * @cfg {Boolean} constrain False to disable constrain to viewport (defaults to true)
83613  * @cfg {String} cls CSS class to add to the element
83614  * @cfg {Number} zindex Starting z-index (defaults to 11000)
83615  * @cfg {Number} shadowOffset Number of pixels to offset the shadow (defaults to 4)
83616  * @cfg {Boolean} useDisplay
83617  * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
83618  * to use css style <tt>'display:none;'</tt> to hide the Layer.
83619  * @cfg {String} visibilityCls The CSS class name to add in order to hide this Layer if this layer
83620  * is configured with <code>{@link #hideMode}: 'asclass'</code>
83621  * @cfg {String} hideMode
83622  * A String which specifies how this Layer will be hidden.
83623  * Values may be<div class="mdetail-params"><ul>
83624  * <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
83625  * <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
83626  * <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
83627  * is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
83628  * in a Component having zero dimensions.</li></ul></div>
83629  * @constructor
83630  * @param {Object} config An object with config options.
83631  * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element. If the element is not found it creates it.
83632  */
83633 Ext.define('Ext.Layer', {
83634     uses: ['Ext.Shadow'],
83635
83636     // shims are shared among layer to keep from having 100 iframes
83637     statics: {
83638         shims: []
83639     },
83640
83641     extend: 'Ext.core.Element',
83642
83643     constructor: function(config, existingEl) {
83644         config = config || {};
83645         var me = this,
83646             dh = Ext.core.DomHelper,
83647             cp = config.parentEl,
83648             pel = cp ? Ext.getDom(cp) : document.body,
83649         hm = config.hideMode;
83650
83651         if (existingEl) {
83652             me.dom = Ext.getDom(existingEl);
83653         }
83654         if (!me.dom) {
83655             me.dom = dh.append(pel, config.dh || {
83656                 tag: 'div',
83657                 cls: Ext.baseCSSPrefix + 'layer'
83658             });
83659         } else {
83660             me.addCls(Ext.baseCSSPrefix + 'layer');
83661             if (!me.dom.parentNode) {
83662                 pel.appendChild(me.dom);
83663             }
83664         }
83665
83666         if (config.cls) {
83667             me.addCls(config.cls);
83668         }
83669         me.constrain = config.constrain !== false;
83670
83671         // Allow Components to pass their hide mode down to the Layer if they are floating.
83672         // Otherwise, allow useDisplay to override the default hiding method which is visibility.
83673         // TODO: Have ExtJS's Element implement visibilityMode by using classes as in Mobile.
83674         if (hm) {
83675             me.setVisibilityMode(Ext.core.Element[hm.toUpperCase()]);
83676             if (me.visibilityMode == Ext.core.Element.ASCLASS) {
83677                 me.visibilityCls = config.visibilityCls;
83678             }
83679         } else if (config.useDisplay) {
83680             me.setVisibilityMode(Ext.core.Element.DISPLAY);
83681         } else {
83682             me.setVisibilityMode(Ext.core.Element.VISIBILITY);
83683         }
83684
83685         if (config.id) {
83686             me.id = me.dom.id = config.id;
83687         } else {
83688             me.id = Ext.id(me.dom);
83689         }
83690         me.position('absolute');
83691         if (config.shadow) {
83692             me.shadowOffset = config.shadowOffset || 4;
83693             me.shadow = Ext.create('Ext.Shadow', {
83694                 offset: me.shadowOffset,
83695                 mode: config.shadow
83696             });
83697             me.disableShadow();
83698         } else {
83699             me.shadowOffset = 0;
83700         }
83701         me.useShim = config.shim !== false && Ext.useShims;
83702         if (config.hidden === true) {
83703             me.hide();
83704         } else {
83705             this.show();
83706         }
83707     },
83708
83709     getZIndex: function() {
83710         return parseInt((this.getShim() || this).getStyle('z-index'), 10);
83711     },
83712
83713     getShim: function() {
83714         var me = this,
83715             shim, pn;
83716
83717         if (!me.useShim) {
83718             return null;
83719         }
83720         if (!me.shim) {
83721             shim = me.self.shims.shift();
83722             if (!shim) {
83723                 shim = me.createShim();
83724                 shim.enableDisplayMode('block');
83725                 shim.hide();
83726             }
83727             pn = me.dom.parentNode;
83728             if (shim.dom.parentNode != pn) {
83729                 pn.insertBefore(shim.dom, me.dom);
83730             }
83731             me.shim = shim;
83732         }
83733         return me.shim;
83734     },
83735
83736     hideShim: function() {
83737         if (this.shim) {
83738             this.shim.setDisplayed(false);
83739             this.self.shims.push(this.shim);
83740             delete this.shim;
83741         }
83742     },
83743
83744     disableShadow: function() {
83745         if (this.shadow) {
83746             this.shadowDisabled = true;
83747             this.shadow.hide();
83748             this.lastShadowOffset = this.shadowOffset;
83749             this.shadowOffset = 0;
83750         }
83751     },
83752
83753     enableShadow: function(show) {
83754         if (this.shadow) {
83755             this.shadowDisabled = false;
83756             this.shadowOffset = this.lastShadowOffset;
83757             delete this.lastShadowOffset;
83758             if (show) {
83759                 this.sync(true);
83760             }
83761         }
83762     },
83763
83764     /**
83765      * @private
83766      * <p>Synchronize this Layer's associated elements, the shadow, and possibly the shim.</p>
83767      * <p>This code can execute repeatedly in milliseconds,
83768      * eg: dragging a Component configured liveDrag: true, or which has no ghost method
83769      * so code size was sacrificed for efficiency (e.g. no getBox/setBox, no XY calls)</p>
83770      * @param {Boolean} doShow Pass true to ensure that the shadow is shown.
83771      */
83772     sync: function(doShow) {
83773         var me = this,
83774             shadow = me.shadow,
83775             shadowPos, shimStyle, shadowSize;
83776
83777         if (!this.updating && this.isVisible() && (shadow || this.useShim)) {
83778             var shim = this.getShim(),
83779                 l = this.getLeft(true),
83780                 t = this.getTop(true),
83781                 w = this.getWidth(),
83782                 h = this.getHeight(),
83783                 shimIndex;
83784
83785             if (shadow && !this.shadowDisabled) {
83786                 if (doShow && !shadow.isVisible()) {
83787                     shadow.show(this);
83788                 } else {
83789                     shadow.realign(l, t, w, h);
83790                 }
83791                 if (shim) {
83792                     // TODO: Determine how the shims zIndex is above the layer zIndex at this point
83793                     shimIndex = shim.getStyle('z-index');
83794                     if (shimIndex > me.zindex) {
83795                         me.shim.setStyle('z-index', me.zindex - 2);
83796                     }
83797                     shim.show();
83798                     // fit the shim behind the shadow, so it is shimmed too
83799                     if (shadow.isVisible()) {
83800                         shadowPos = shadow.el.getXY();
83801                         shimStyle = shim.dom.style;
83802                         shadowSize = shadow.el.getSize();
83803                         shimStyle.left = (shadowPos[0]) + 'px';
83804                         shimStyle.top = (shadowPos[1]) + 'px';
83805                         shimStyle.width = (shadowSize.width) + 'px';
83806                         shimStyle.height = (shadowSize.height) + 'px';
83807                     } else {
83808                         shim.setSize(w, h);
83809                         shim.setLeftTop(l, t);
83810                     }
83811                 }
83812             } else if (shim) {
83813                 // TODO: Determine how the shims zIndex is above the layer zIndex at this point
83814                 shimIndex = shim.getStyle('z-index');
83815                 if (shimIndex > me.zindex) {
83816                     me.shim.setStyle('z-index', me.zindex - 2);
83817                 }
83818                 shim.show();
83819                 shim.setSize(w, h);
83820                 shim.setLeftTop(l, t);
83821             }
83822         }
83823         return this;
83824     },
83825
83826     remove: function() {
83827         this.hideUnders();
83828         this.callParent();
83829     },
83830
83831     // private
83832     beginUpdate: function() {
83833         this.updating = true;
83834     },
83835
83836     // private
83837     endUpdate: function() {
83838         this.updating = false;
83839         this.sync(true);
83840     },
83841
83842     // private
83843     hideUnders: function() {
83844         if (this.shadow) {
83845             this.shadow.hide();
83846         }
83847         this.hideShim();
83848     },
83849
83850     // private
83851     constrainXY: function() {
83852         if (this.constrain) {
83853             var vw = Ext.core.Element.getViewWidth(),
83854                 vh = Ext.core.Element.getViewHeight(),
83855                 s = Ext.getDoc().getScroll(),
83856                 xy = this.getXY(),
83857                 x = xy[0],
83858                 y = xy[1],
83859                 so = this.shadowOffset,
83860                 w = this.dom.offsetWidth + so,
83861                 h = this.dom.offsetHeight + so,
83862                 moved = false; // only move it if it needs it
83863             // first validate right/bottom
83864             if ((x + w) > vw + s.left) {
83865                 x = vw - w - so;
83866                 moved = true;
83867             }
83868             if ((y + h) > vh + s.top) {
83869                 y = vh - h - so;
83870                 moved = true;
83871             }
83872             // then make sure top/left isn't negative
83873             if (x < s.left) {
83874                 x = s.left;
83875                 moved = true;
83876             }
83877             if (y < s.top) {
83878                 y = s.top;
83879                 moved = true;
83880             }
83881             if (moved) {
83882                 Ext.Layer.superclass.setXY.call(this, [x, y]);
83883                 this.sync();
83884             }
83885         }
83886         return this;
83887     },
83888
83889     getConstrainOffset: function() {
83890         return this.shadowOffset;
83891     },
83892
83893     // overridden Element method
83894     setVisible: function(visible, animate, duration, callback, easing) {
83895         var me = this,
83896             cb;
83897
83898         // post operation processing
83899         cb = function() {
83900             if (visible) {
83901                 me.sync(true);
83902             }
83903             if (callback) {
83904                 callback();
83905             }
83906         };
83907
83908         // Hide shadow and shim if hiding
83909         if (!visible) {
83910             this.hideUnders(true);
83911         }
83912         this.callParent([visible, animate, duration, callback, easing]);
83913         if (!animate) {
83914             cb();
83915         }
83916         return this;
83917     },
83918
83919     // private
83920     beforeFx: function() {
83921         this.beforeAction();
83922         return this.callParent(arguments);
83923     },
83924
83925     // private
83926     afterFx: function() {
83927         this.callParent(arguments);
83928         this.sync(this.isVisible());
83929     },
83930
83931     // private
83932     beforeAction: function() {
83933         if (!this.updating && this.shadow) {
83934             this.shadow.hide();
83935         }
83936     },
83937
83938     // overridden Element method
83939     setLeft: function(left) {
83940         this.callParent(arguments);
83941         return this.sync();
83942     },
83943
83944     setTop: function(top) {
83945         this.callParent(arguments);
83946         return this.sync();
83947     },
83948
83949     setLeftTop: function(left, top) {
83950         this.callParent(arguments);
83951         return this.sync();
83952     },
83953
83954     setXY: function(xy, animate, duration, callback, easing) {
83955
83956         // Callback will restore shadow state and call the passed callback
83957         callback = this.createCB(callback);
83958
83959         this.fixDisplay();
83960         this.beforeAction();
83961         this.callParent([xy, animate, duration, callback, easing]);
83962         if (!animate) {
83963             callback();
83964         }
83965         return this;
83966     },
83967
83968     // private
83969     createCB: function(callback) {
83970         var me = this,
83971             showShadow = me.shadow && me.shadow.isVisible();
83972
83973         return function() {
83974             me.constrainXY();
83975             me.sync(showShadow);
83976             if (callback) {
83977                 callback();
83978             }
83979         };
83980     },
83981
83982     // overridden Element method
83983     setX: function(x, animate, duration, callback, easing) {
83984         this.setXY([x, this.getY()], animate, duration, callback, easing);
83985         return this;
83986     },
83987
83988     // overridden Element method
83989     setY: function(y, animate, duration, callback, easing) {
83990         this.setXY([this.getX(), y], animate, duration, callback, easing);
83991         return this;
83992     },
83993
83994     // overridden Element method
83995     setSize: function(w, h, animate, duration, callback, easing) {
83996         // Callback will restore shadow state and call the passed callback
83997         callback = this.createCB(callback);
83998
83999         this.beforeAction();
84000         this.callParent([w, h, animate, duration, callback, easing]);
84001         if (!animate) {
84002             callback();
84003         }
84004         return this;
84005     },
84006
84007     // overridden Element method
84008     setWidth: function(w, animate, duration, callback, easing) {
84009         // Callback will restore shadow state and call the passed callback
84010         callback = this.createCB(callback);
84011
84012         this.beforeAction();
84013         this.callParent([w, animate, duration, callback, easing]);
84014         if (!animate) {
84015             callback();
84016         }
84017         return this;
84018     },
84019
84020     // overridden Element method
84021     setHeight: function(h, animate, duration, callback, easing) {
84022         // Callback will restore shadow state and call the passed callback
84023         callback = this.createCB(callback);
84024
84025         this.beforeAction();
84026         this.callParent([h, animate, duration, callback, easing]);
84027         if (!animate) {
84028             callback();
84029         }
84030         return this;
84031     },
84032
84033     // overridden Element method
84034     setBounds: function(x, y, width, height, animate, duration, callback, easing) {
84035         // Callback will restore shadow state and call the passed callback
84036         callback = this.createCB(callback);
84037
84038         this.beforeAction();
84039         if (!animate) {
84040             Ext.Layer.superclass.setXY.call(this, [x, y]);
84041             Ext.Layer.superclass.setSize.call(this, width, height);
84042             callback();
84043         } else {
84044             this.callParent([x, y, width, height, animate, duration, callback, easing]);
84045         }
84046         return this;
84047     },
84048
84049     /**
84050      * <p>Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
84051      * incremented depending upon the presence of a shim or a shadow in so that it always shows above those two associated elements.</p>
84052      * <p>Any shim, will be assigned the passed z-index. A shadow will be assigned the next highet z-index, and the Layer's
84053      * element will receive the highest  z-index.
84054      * @param {Number} zindex The new z-index to set
84055      * @return {this} The Layer
84056      */
84057     setZIndex: function(zindex) {
84058         this.zindex = zindex;
84059         if (this.getShim()) {
84060             this.shim.setStyle('z-index', zindex++);
84061         }
84062         if (this.shadow) {
84063             this.shadow.setZIndex(zindex++);
84064         }
84065         this.setStyle('z-index', zindex);
84066         return this;
84067     }
84068 });
84069
84070 /**
84071  * @class Ext.layout.component.ProgressBar
84072  * @extends Ext.layout.component.Component
84073  * @private
84074  */
84075
84076 Ext.define('Ext.layout.component.ProgressBar', {
84077
84078     /* Begin Definitions */
84079
84080     alias: ['layout.progressbar'],
84081
84082     extend: 'Ext.layout.component.Component',
84083
84084     /* End Definitions */
84085
84086     type: 'progressbar',
84087
84088     onLayout: function(width, height) {
84089         var me = this,
84090             owner = me.owner,
84091             textEl = owner.textEl;
84092         
84093         me.setElementSize(owner.el, width, height);
84094         textEl.setWidth(owner.el.getWidth(true));
84095         
84096         me.callParent([width, height]);
84097         
84098         owner.updateProgress(owner.value);
84099     }
84100 });
84101 /**
84102  * @class Ext.ProgressBar
84103  * @extends Ext.Component
84104  * <p>An updateable progress bar component.  The progress bar supports two different modes: manual and automatic.</p>
84105  * <p>In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the
84106  * progress bar as needed from your own code.  This method is most appropriate when you want to show progress
84107  * throughout an operation that has predictable points of interest at which you can update the control.</p>
84108  * <p>In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it
84109  * once the operation is complete.  You can optionally have the progress bar wait for a specific amount of time
84110  * and then clear itself.  Automatic mode is most appropriate for timed operations or asynchronous operations in
84111  * which you have no need for indicating intermediate progress.</p>
84112  * {@img Ext.ProgressBar/Ext.ProgressBar.png Ext.ProgressBar component}
84113  * Example Usage:
84114      var p = Ext.create('Ext.ProgressBar', {
84115        renderTo: Ext.getBody(),
84116        width: 300
84117     });
84118
84119     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
84120     p.wait({
84121        interval: 500, //bar will move fast!
84122        duration: 50000,
84123        increment: 15,
84124        text: 'Updating...',
84125        scope: this,
84126        fn: function(){
84127           p.updateText('Done!');
84128        }
84129     });
84130  * @cfg {Float} value A floating point value between 0 and 1 (e.g., .5, defaults to 0)
84131  * @cfg {String} text The progress bar text (defaults to '')
84132  * @cfg {Mixed} textEl The element to render the progress text to (defaults to the progress
84133  * bar's internal text element)
84134  * @cfg {String} id The progress bar element's id (defaults to an auto-generated id)
84135  * @xtype progressbar
84136  */
84137 Ext.define('Ext.ProgressBar', {
84138     extend: 'Ext.Component',
84139     alias: 'widget.progressbar',
84140
84141     requires: [
84142         'Ext.Template',
84143         'Ext.CompositeElement',
84144         'Ext.TaskManager',
84145         'Ext.layout.component.ProgressBar'
84146     ],
84147
84148     uses: ['Ext.fx.Anim'],
84149    /**
84150     * @cfg {String} baseCls
84151     * The base CSS class to apply to the progress bar's wrapper element (defaults to 'x-progress')
84152     */
84153     baseCls: Ext.baseCSSPrefix + 'progress',
84154
84155     config: {
84156         /**
84157         * @cfg {Boolean} animate
84158         * True to animate the progress bar during transitions (defaults to false)
84159         */
84160         animate: false,
84161
84162         /**
84163          * @cfg {String} text The text shown in the progress bar (defaults to '')
84164          */
84165         text: ''
84166     },
84167
84168     // private
84169     waitTimer: null,
84170
84171     renderTpl: [
84172         '<div class="{baseCls}-text {baseCls}-text-back">',
84173             '<div>&#160;</div>',
84174         '</div>',
84175         '<div class="{baseCls}-bar">',
84176             '<div class="{baseCls}-text">',
84177                 '<div>&#160;</div>',
84178             '</div>',
84179         '</div>'
84180     ],
84181
84182     componentLayout: 'progressbar',
84183
84184     // private
84185     initComponent: function() {
84186         this.callParent();
84187
84188         this.renderSelectors = Ext.apply(this.renderSelectors || {}, {
84189             textTopEl: '.' + this.baseCls + '-text',
84190             textBackEl: '.' + this.baseCls + '-text-back',
84191             bar: '.' + this.baseCls + '-bar'
84192         });
84193
84194         this.addEvents(
84195             /**
84196              * @event update
84197              * Fires after each update interval
84198              * @param {Ext.ProgressBar} this
84199              * @param {Number} The current progress value
84200              * @param {String} The current progress text
84201              */
84202             "update"
84203         );
84204     },
84205
84206     afterRender : function() {
84207         var me = this;
84208
84209         me.textEl = me.textEl ? Ext.get(me.textEl) : me.el.select('.' + me.baseCls + '-text');
84210
84211         this.callParent(arguments);
84212
84213         if (me.value) {
84214             me.updateProgress(me.value, me.text);
84215         }
84216         else {
84217             me.updateText(me.text);
84218         }
84219     },
84220
84221     /**
84222      * Updates the progress bar value, and optionally its text.  If the text argument is not specified,
84223      * any existing text value will be unchanged.  To blank out existing text, pass ''.  Note that even
84224      * if the progress bar value exceeds 1, it will never automatically reset -- you are responsible for
84225      * determining when the progress is complete and calling {@link #reset} to clear and/or hide the control.
84226      * @param {Float} value (optional) A floating point value between 0 and 1 (e.g., .5, defaults to 0)
84227      * @param {String} text (optional) The string to display in the progress text element (defaults to '')
84228      * @param {Boolean} animate (optional) Whether to animate the transition of the progress bar. If this value is
84229      * not specified, the default for the class is used (default to false)
84230      * @return {Ext.ProgressBar} this
84231      */
84232     updateProgress: function(value, text, animate) {
84233         var newWidth;
84234         this.value = value || 0;
84235         if (text) {
84236             this.updateText(text);
84237         }
84238         if (this.rendered && !this.isDestroyed) {
84239             newWidth = Math.floor(this.value * this.el.getWidth(true));
84240             if (Ext.isForcedBorderBox) {
84241                 newWidth += this.bar.getBorderWidth("lr");
84242             }
84243             if (animate === true || (animate !== false && this.animate)) {
84244                 this.bar.stopAnimation();
84245                 this.bar.animate(Ext.apply({
84246                     to: {
84247                         width: newWidth + 'px'
84248                     }
84249                 }, this.animate));
84250             } else {
84251                 this.bar.setWidth(newWidth);
84252             }
84253         }
84254         this.fireEvent('update', this, this.value, text);
84255         return this;
84256     },
84257
84258     /**
84259      * Updates the progress bar text.  If specified, textEl will be updated, otherwise the progress
84260      * bar itself will display the updated text.
84261      * @param {String} text (optional) The string to display in the progress text element (defaults to '')
84262      * @return {Ext.ProgressBar} this
84263      */
84264     updateText: function(text) {
84265         this.text = text;
84266         if (this.rendered) {
84267             this.textEl.update(this.text);
84268         }
84269         return this;
84270     },
84271
84272     applyText : function(text) {
84273         this.updateText(text);
84274     },
84275
84276     /**
84277          * Initiates an auto-updating progress bar.  A duration can be specified, in which case the progress
84278          * bar will automatically reset after a fixed amount of time and optionally call a callback function
84279          * if specified.  If no duration is passed in, then the progress bar will run indefinitely and must
84280          * be manually cleared by calling {@link #reset}.  The wait method accepts a config object with
84281          * the following properties:
84282          * <pre>
84283     Property   Type          Description
84284     ---------- ------------  ----------------------------------------------------------------------
84285     duration   Number        The length of time in milliseconds that the progress bar should
84286                              run before resetting itself (defaults to undefined, in which case it
84287                              will run indefinitely until reset is called)
84288     interval   Number        The length of time in milliseconds between each progress update
84289                              (defaults to 1000 ms)
84290     animate    Boolean       Whether to animate the transition of the progress bar. If this value is
84291                              not specified, the default for the class is used.
84292     increment  Number        The number of progress update segments to display within the progress
84293                              bar (defaults to 10).  If the bar reaches the end and is still
84294                              updating, it will automatically wrap back to the beginning.
84295     text       String        Optional text to display in the progress bar element (defaults to '').
84296     fn         Function      A callback function to execute after the progress bar finishes auto-
84297                              updating.  The function will be called with no arguments.  This function
84298                              will be ignored if duration is not specified since in that case the
84299                              progress bar can only be stopped programmatically, so any required function
84300                              should be called by the same code after it resets the progress bar.
84301     scope      Object        The scope that is passed to the callback function (only applies when
84302                              duration and fn are both passed).
84303     </pre>
84304              *
84305              * Example usage:
84306              * <pre><code>
84307     var p = new Ext.ProgressBar({
84308        renderTo: 'my-el'
84309     });
84310
84311     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
84312     var p = Ext.create('Ext.ProgressBar', {
84313        renderTo: Ext.getBody(),
84314        width: 300
84315     });
84316
84317     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
84318     p.wait({
84319        interval: 500, //bar will move fast!
84320        duration: 50000,
84321        increment: 15,
84322        text: 'Updating...',
84323        scope: this,
84324        fn: function(){
84325           p.updateText('Done!');
84326        }
84327     });
84328
84329     //Or update indefinitely until some async action completes, then reset manually
84330     p.wait();
84331     myAction.on('complete', function(){
84332         p.reset();
84333         p.updateText('Done!');
84334     });
84335     </code></pre>
84336          * @param {Object} config (optional) Configuration options
84337          * @return {Ext.ProgressBar} this
84338          */
84339     wait: function(o) {
84340         if (!this.waitTimer) {
84341             var scope = this;
84342             o = o || {};
84343             this.updateText(o.text);
84344             this.waitTimer = Ext.TaskManager.start({
84345                 run: function(i){
84346                     var inc = o.increment || 10;
84347                     i -= 1;
84348                     this.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
84349                 },
84350                 interval: o.interval || 1000,
84351                 duration: o.duration,
84352                 onStop: function(){
84353                     if (o.fn) {
84354                         o.fn.apply(o.scope || this);
84355                     }
84356                     this.reset();
84357                 },
84358                 scope: scope
84359             });
84360         }
84361         return this;
84362     },
84363
84364     /**
84365      * Returns true if the progress bar is currently in a {@link #wait} operation
84366      * @return {Boolean} True if waiting, else false
84367      */
84368     isWaiting: function(){
84369         return this.waitTimer !== null;
84370     },
84371
84372     /**
84373      * Resets the progress bar value to 0 and text to empty string.  If hide = true, the progress
84374      * bar will also be hidden (using the {@link #hideMode} property internally).
84375      * @param {Boolean} hide (optional) True to hide the progress bar (defaults to false)
84376      * @return {Ext.ProgressBar} this
84377      */
84378     reset: function(hide){
84379         this.updateProgress(0);
84380         this.clearTimer();
84381         if (hide === true) {
84382             this.hide();
84383         }
84384         return this;
84385     },
84386
84387     // private
84388     clearTimer: function(){
84389         if (this.waitTimer) {
84390             this.waitTimer.onStop = null; //prevent recursion
84391             Ext.TaskManager.stop(this.waitTimer);
84392             this.waitTimer = null;
84393         }
84394     },
84395
84396     onDestroy: function(){
84397         this.clearTimer();
84398         if (this.rendered) {
84399             if (this.textEl.isComposite) {
84400                 this.textEl.clear();
84401             }
84402             Ext.destroyMembers(this, 'textEl', 'progressBar', 'textTopEl');
84403         }
84404         this.callParent();
84405     }
84406 });
84407
84408 /**
84409  * @class Ext.ShadowPool
84410  * @extends Object
84411  * Private utility class that manages the internal Shadow cache
84412  * @private
84413  */
84414 Ext.define('Ext.ShadowPool', {
84415     singleton: true,
84416     requires: ['Ext.core.DomHelper'],
84417
84418     markup: function() {
84419         if (Ext.supports.CSS3BoxShadow) {
84420             return '<div class="' + Ext.baseCSSPrefix + 'css-shadow" role="presentation"></div>';
84421         } else if (Ext.isIE) {
84422             return '<div class="' + Ext.baseCSSPrefix + 'ie-shadow" role="presentation"></div>';
84423         } else {
84424             return '<div class="' + Ext.baseCSSPrefix + 'frame-shadow" role="presentation">' +
84425                 '<div class="xst" role="presentation">' +
84426                     '<div class="xstl" role="presentation"></div>' +
84427                     '<div class="xstc" role="presentation"></div>' +
84428                     '<div class="xstr" role="presentation"></div>' +
84429                 '</div>' +
84430                 '<div class="xsc" role="presentation">' +
84431                     '<div class="xsml" role="presentation"></div>' +
84432                     '<div class="xsmc" role="presentation"></div>' +
84433                     '<div class="xsmr" role="presentation"></div>' +
84434                 '</div>' +
84435                 '<div class="xsb" role="presentation">' +
84436                     '<div class="xsbl" role="presentation"></div>' +
84437                     '<div class="xsbc" role="presentation"></div>' +
84438                     '<div class="xsbr" role="presentation"></div>' +
84439                 '</div>' +
84440             '</div>';
84441         }
84442     }(),
84443
84444     shadows: [],
84445
84446     pull: function() {
84447         var sh = this.shadows.shift();
84448         if (!sh) {
84449             sh = Ext.get(Ext.core.DomHelper.insertHtml("beforeBegin", document.body.firstChild, this.markup));
84450             sh.autoBoxAdjust = false;
84451         }
84452         return sh;
84453     },
84454
84455     push: function(sh) {
84456         this.shadows.push(sh);
84457     },
84458     
84459     reset: function() {
84460         Ext.Array.each(this.shadows, function(shadow) {
84461             shadow.remove();
84462         });
84463         this.shadows = [];
84464     }
84465 });
84466 /**
84467  * @class Ext.Shadow
84468  * Simple class that can provide a shadow effect for any element.  Note that the element MUST be absolutely positioned,
84469  * and the shadow does not provide any shimming.  This should be used only in simple cases -- for more advanced
84470  * functionality that can also provide the same shadow effect, see the {@link Ext.Layer} class.
84471  * @constructor
84472  * Create a new Shadow
84473  * @param {Object} config The config object
84474  */
84475 Ext.define('Ext.Shadow', {
84476     requires: ['Ext.ShadowPool'],
84477
84478     constructor: function(config) {
84479         Ext.apply(this, config);
84480         if (typeof this.mode != "string") {
84481             this.mode = this.defaultMode;
84482         }
84483         var offset = this.offset,
84484             adjusts = {
84485                 h: 0
84486             },
84487             rad = Math.floor(this.offset / 2);
84488
84489         switch (this.mode.toLowerCase()) {
84490             // all this hideous nonsense calculates the various offsets for shadows
84491             case "drop":
84492                 if (Ext.supports.CSS3BoxShadow) {
84493                     adjusts.w = adjusts.h = -offset;
84494                     adjusts.l = adjusts.t = offset;
84495                 } else {
84496                     adjusts.w = 0;
84497                     adjusts.l = adjusts.t = offset;
84498                     adjusts.t -= 1;
84499                     if (Ext.isIE) {
84500                         adjusts.l -= offset + rad;
84501                         adjusts.t -= offset + rad;
84502                         adjusts.w -= rad;
84503                         adjusts.h -= rad;
84504                         adjusts.t += 1;
84505                     }
84506                 }
84507                 break;
84508             case "sides":
84509                 if (Ext.supports.CSS3BoxShadow) {
84510                     adjusts.h -= offset;
84511                     adjusts.t = offset;
84512                     adjusts.l = adjusts.w = 0;
84513                 } else {
84514                     adjusts.w = (offset * 2);
84515                     adjusts.l = -offset;
84516                     adjusts.t = offset - 1;
84517                     if (Ext.isIE) {
84518                         adjusts.l -= (offset - rad);
84519                         adjusts.t -= offset + rad;
84520                         adjusts.l += 1;
84521                         adjusts.w -= (offset - rad) * 2;
84522                         adjusts.w -= rad + 1;
84523                         adjusts.h -= 1;
84524                     }
84525                 }
84526                 break;
84527             case "frame":
84528                 if (Ext.supports.CSS3BoxShadow) {
84529                     adjusts.l = adjusts.w = adjusts.t = 0;
84530                 } else {
84531                     adjusts.w = adjusts.h = (offset * 2);
84532                     adjusts.l = adjusts.t = -offset;
84533                     adjusts.t += 1;
84534                     adjusts.h -= 2;
84535                     if (Ext.isIE) {
84536                         adjusts.l -= (offset - rad);
84537                         adjusts.t -= (offset - rad);
84538                         adjusts.l += 1;
84539                         adjusts.w -= (offset + rad + 1);
84540                         adjusts.h -= (offset + rad);
84541                         adjusts.h += 1;
84542                     }
84543                     break;
84544                 }
84545         }
84546         this.adjusts = adjusts;
84547     },
84548
84549     /**
84550      * @cfg {String} mode
84551      * The shadow display mode.  Supports the following options:<div class="mdetail-params"><ul>
84552      * <li><b><tt>sides</tt></b> : Shadow displays on both sides and bottom only</li>
84553      * <li><b><tt>frame</tt></b> : Shadow displays equally on all four sides</li>
84554      * <li><b><tt>drop</tt></b> : Traditional bottom-right drop shadow</li>
84555      * </ul></div>
84556      */
84557     /**
84558      * @cfg {String} offset
84559      * The number of pixels to offset the shadow from the element (defaults to <tt>4</tt>)
84560      */
84561     offset: 4,
84562
84563     // private
84564     defaultMode: "drop",
84565
84566     /**
84567      * Displays the shadow under the target element
84568      * @param {Mixed} targetEl The id or element under which the shadow should display
84569      */
84570     show: function(target) {
84571         target = Ext.get(target);
84572         if (!this.el) {
84573             this.el = Ext.ShadowPool.pull();
84574             if (this.el.dom.nextSibling != target.dom) {
84575                 this.el.insertBefore(target);
84576             }
84577         }
84578         this.el.setStyle("z-index", this.zIndex || parseInt(target.getStyle("z-index"), 10) - 1);
84579         if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
84580             this.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (this.offset) + ")";
84581         }
84582         this.realign(
84583             target.getLeft(true),
84584             target.getTop(true),
84585             target.getWidth(),
84586             target.getHeight()
84587         );
84588         this.el.dom.style.display = "block";
84589     },
84590
84591     /**
84592      * Returns true if the shadow is visible, else false
84593      */
84594     isVisible: function() {
84595         return this.el ? true: false;
84596     },
84597
84598     /**
84599      * Direct alignment when values are already available. Show must be called at least once before
84600      * calling this method to ensure it is initialized.
84601      * @param {Number} left The target element left position
84602      * @param {Number} top The target element top position
84603      * @param {Number} width The target element width
84604      * @param {Number} height The target element height
84605      */
84606     realign: function(l, t, targetWidth, targetHeight) {
84607         if (!this.el) {
84608             return;
84609         }
84610         var adjusts = this.adjusts,
84611             d = this.el.dom,
84612             targetStyle = d.style,
84613             shadowWidth,
84614             shadowHeight,
84615             cn,
84616             sww, 
84617             sws, 
84618             shs;
84619
84620         targetStyle.left = (l + adjusts.l) + "px";
84621         targetStyle.top = (t + adjusts.t) + "px";
84622         shadowWidth = Math.max(targetWidth + adjusts.w, 0);
84623         shadowHeight = Math.max(targetHeight + adjusts.h, 0);
84624         sws = shadowWidth + "px";
84625         shs = shadowHeight + "px";
84626         if (targetStyle.width != sws || targetStyle.height != shs) {
84627             targetStyle.width = sws;
84628             targetStyle.height = shs;
84629             if (Ext.supports.CSS3BoxShadow) {
84630                 targetStyle.boxShadow = '0 0 ' + this.offset + 'px 0 #888';
84631             } else {
84632
84633                 // Adjust the 9 point framed element to poke out on the required sides
84634                 if (!Ext.isIE) {
84635                     cn = d.childNodes;
84636                     sww = Math.max(0, (shadowWidth - 12)) + "px";
84637                     cn[0].childNodes[1].style.width = sww;
84638                     cn[1].childNodes[1].style.width = sww;
84639                     cn[2].childNodes[1].style.width = sww;
84640                     cn[1].style.height = Math.max(0, (shadowHeight - 12)) + "px";
84641                 }
84642             }
84643         }
84644     },
84645
84646     /**
84647      * Hides this shadow
84648      */
84649     hide: function() {
84650         if (this.el) {
84651             this.el.dom.style.display = "none";
84652             Ext.ShadowPool.push(this.el);
84653             delete this.el;
84654         }
84655     },
84656
84657     /**
84658      * Adjust the z-index of this shadow
84659      * @param {Number} zindex The new z-index
84660      */
84661     setZIndex: function(z) {
84662         this.zIndex = z;
84663         if (this.el) {
84664             this.el.setStyle("z-index", z);
84665         }
84666     }
84667 });
84668 /**
84669  * @class Ext.button.Split
84670  * @extends Ext.button.Button
84671  * A split button that provides a built-in dropdown arrow that can fire an event separately from the default
84672  * click event of the button.  Typically this would be used to display a dropdown menu that provides additional
84673  * options to the primary button action, but any custom handler can provide the arrowclick implementation.  
84674  * {@img Ext.button.Split/Ext.button.Split.png Ext.button.Split component}
84675  * Example usage:
84676  * <pre><code>
84677 // display a dropdown menu:
84678     Ext.create('Ext.button.Split', {
84679         renderTo: 'button-ct', // the container id
84680         text: 'Options',
84681         handler: optionsHandler, // handle a click on the button itself
84682         menu: new Ext.menu.Menu({
84683         items: [
84684                 // these items will render as dropdown menu items when the arrow is clicked:
84685                 {text: 'Item 1', handler: item1Handler},
84686                 {text: 'Item 2', handler: item2Handler}
84687         ]
84688         })
84689     });
84690
84691 // Instead of showing a menu, you provide any type of custom
84692 // functionality you want when the dropdown arrow is clicked:
84693     Ext.create('Ext.button.Split', {
84694         renderTo: 'button-ct',
84695         text: 'Options',
84696         handler: optionsHandler,
84697         arrowHandler: myCustomHandler
84698     });
84699 </code></pre>
84700  * @cfg {Function} arrowHandler A function called when the arrow button is clicked (can be used instead of click event)
84701  * @cfg {String} arrowTooltip The title attribute of the arrow
84702  * @constructor
84703  * Create a new menu button
84704  * @param {Object} config The config object
84705  * @xtype splitbutton
84706  */
84707
84708 Ext.define('Ext.button.Split', {
84709
84710     /* Begin Definitions */
84711
84712     alias: 'widget.splitbutton',
84713
84714     extend: 'Ext.button.Button',
84715     alternateClassName: 'Ext.SplitButton',
84716
84717     // private
84718     arrowCls      : 'split',
84719     split         : true,
84720
84721     // private
84722     initComponent : function(){
84723         this.callParent();
84724         /**
84725          * @event arrowclick
84726          * Fires when this button's arrow is clicked
84727          * @param {MenuButton} this
84728          * @param {EventObject} e The click event
84729          */
84730         this.addEvents("arrowclick");
84731     },
84732
84733      /**
84734      * Sets this button's arrow click handler.
84735      * @param {Function} handler The function to call when the arrow is clicked
84736      * @param {Object} scope (optional) Scope for the function passed above
84737      */
84738     setArrowHandler : function(handler, scope){
84739         this.arrowHandler = handler;
84740         this.scope = scope;
84741     },
84742
84743     // private
84744     onClick : function(e, t) {
84745         var me = this;
84746         
84747         e.preventDefault();
84748         if (!me.disabled) {
84749             if (me.overMenuTrigger) {
84750                 if (me.menu && !me.menu.isVisible() && !me.ignoreNextClick) {
84751                     me.showMenu();
84752                 }
84753                 me.fireEvent("arrowclick", me, e);
84754                 if (me.arrowHandler) {
84755                     me.arrowHandler.call(me.scope || me, me, e);
84756                 }
84757             } else {
84758                 if (me.enableToggle) {
84759                     me.toggle();
84760                 }
84761                 me.fireEvent("click", me, e);
84762                 if (me.handler) {
84763                     me.handler.call(me.scope || me, me, e);
84764                 }
84765                 me.onBlur();
84766             }
84767         }
84768     }
84769 });
84770 /**
84771  * @class Ext.button.Cycle
84772  * @extends Ext.button.Split
84773  * A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements.  The button automatically
84774  * cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
84775  * {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
84776  * button displays the dropdown menu just like a normal SplitButton.  
84777  * {@img Ext.button.Cycle/Ext.button.Cycle.png Ext.button.Cycle component}
84778  * Example usage:
84779  * <pre><code>
84780 Ext.create('Ext.button.Cycle', {
84781     showText: true,
84782     prependText: 'View as ',
84783     renderTo: Ext.getBody(),
84784     menu: {
84785         id: 'view-type-menu',
84786         items: [{
84787             text:'text only',
84788             iconCls:'view-text',
84789             checked:true
84790         },{
84791             text:'HTML',
84792             iconCls:'view-html'
84793         }]
84794     },
84795     changeHandler:function(cycleBtn, activeItem){
84796         Ext.Msg.alert('Change View', activeItem.text);
84797     }
84798 });
84799 </code></pre>
84800  * @constructor
84801  * Create a new split button
84802  * @param {Object} config The config object
84803  * @xtype cycle
84804  */
84805
84806 Ext.define('Ext.button.Cycle', {
84807
84808     /* Begin Definitions */
84809
84810     alias: 'widget.cycle',
84811
84812     extend: 'Ext.button.Split',
84813     alternateClassName: 'Ext.CycleButton',
84814
84815     /* End Definitions */
84816
84817     /**
84818      * @cfg {Array} items <p>Deprecated as of 4.0. Use the {@link #menu} config instead. All menu items will be created
84819      * as {@link Ext.menu.CheckItem CheckItem}s.</p>
84820      * <p>An array of {@link Ext.menu.CheckItem} <b>config</b> objects to be used when creating the
84821      * button's menu items (e.g., {text:'Foo', iconCls:'foo-icon'})
84822      */
84823     /**
84824      * @cfg {Boolean} showText True to display the active item's text as the button text (defaults to false).
84825      * The Button will show its configured {@link #text} if this. config is omitted.
84826      */
84827     /**
84828      * @cfg {String} prependText A static string to prepend before the active item's text when displayed as the
84829      * button's text (only applies when showText = true, defaults to '')
84830      */
84831     /**
84832      * @cfg {Function} changeHandler A callback function that will be invoked each time the active menu
84833      * item in the button's menu has changed.  If this callback is not supplied, the SplitButton will instead
84834      * fire the {@link #change} event on active item change.  The changeHandler function will be called with the
84835      * following argument list: (SplitButton this, Ext.menu.CheckItem item)
84836      */
84837     /**
84838      * @cfg {String} forceIcon A css class which sets an image to be used as the static icon for this button.  This
84839      * icon will always be displayed regardless of which item is selected in the dropdown list.  This overrides the 
84840      * default behavior of changing the button's icon to match the selected item's icon on change.
84841      */
84842     /**
84843      * @property menu
84844      * @type Menu
84845      * The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the available choices.
84846      */
84847
84848     // private
84849     getButtonText: function(item) {
84850         var me = this,
84851             text = '';
84852
84853         if (item && me.showText === true) {
84854             if (me.prependText) {
84855                 text += me.prependText;
84856             }
84857             text += item.text;
84858             return text;
84859         }
84860         return me.text;
84861     },
84862
84863     /**
84864      * Sets the button's active menu item.
84865      * @param {Ext.menu.CheckItem} item The item to activate
84866      * @param {Boolean} suppressEvent True to prevent the button's change event from firing (defaults to false)
84867      */
84868     setActiveItem: function(item, suppressEvent) {
84869         var me = this;
84870
84871         if (!Ext.isObject(item)) {
84872             item = me.menu.getComponent(item);
84873         }
84874         if (item) {
84875             if (!me.rendered) {
84876                 me.text = me.getButtonText(item);
84877                 me.iconCls = item.iconCls;
84878             } else {
84879                 me.setText(me.getButtonText(item));
84880                 me.setIconCls(item.iconCls);
84881             }
84882             me.activeItem = item;
84883             if (!item.checked) {
84884                 item.setChecked(true, false);
84885             }
84886             if (me.forceIcon) {
84887                 me.setIconCls(me.forceIcon);
84888             }
84889             if (!suppressEvent) {
84890                 me.fireEvent('change', me, item);
84891             }
84892         }
84893     },
84894
84895     /**
84896      * Gets the currently active menu item.
84897      * @return {Ext.menu.CheckItem} The active item
84898      */
84899     getActiveItem: function() {
84900         return this.activeItem;
84901     },
84902
84903     // private
84904     initComponent: function() {
84905         var me = this,
84906             checked = 0,
84907             items;
84908
84909         me.addEvents(
84910             /**
84911              * @event change
84912              * Fires after the button's active menu item has changed.  Note that if a {@link #changeHandler} function
84913              * is set on this CycleButton, it will be called instead on active item change and this change event will
84914              * not be fired.
84915              * @param {Ext.button.Cycle} this
84916              * @param {Ext.menu.CheckItem} item The menu item that was selected
84917              */
84918             "change"
84919         );
84920
84921         if (me.changeHandler) {
84922             me.on('change', me.changeHandler, me.scope || me);
84923             delete me.changeHandler;
84924         }
84925
84926         // Allow them to specify a menu config which is a standard Button config.
84927         // Remove direct use of "items" in 5.0.
84928         items = (me.menu.items||[]).concat(me.items||[]);
84929         me.menu = Ext.applyIf({
84930             cls: Ext.baseCSSPrefix + 'cycle-menu',
84931             items: []
84932         }, me.menu);
84933
84934         // Convert all items to CheckItems
84935         Ext.each(items, function(item, i) {
84936             item = Ext.applyIf({
84937                 group: me.id,
84938                 itemIndex: i,
84939                 checkHandler: me.checkHandler,
84940                 scope: me,
84941                 checked: item.checked || false
84942             }, item);
84943             me.menu.items.push(item);
84944             if (item.checked) {
84945                 checked = i;
84946             }
84947         });
84948         me.itemCount = me.menu.items.length;
84949         me.callParent(arguments);
84950         me.on('click', me.toggleSelected, me);
84951         me.setActiveItem(checked, me);
84952
84953         // If configured with a fixed width, the cycling will center a different child item's text each click. Prevent this.
84954         if (me.width && me.showText) {
84955             me.addCls(Ext.baseCSSPrefix + 'cycle-fixed-width');
84956         }
84957     },
84958
84959     // private
84960     checkHandler: function(item, pressed) {
84961         if (pressed) {
84962             this.setActiveItem(item);
84963         }
84964     },
84965
84966     /**
84967      * This is normally called internally on button click, but can be called externally to advance the button's
84968      * active item programmatically to the next one in the menu.  If the current item is the last one in the menu
84969      * the active item will be set to the first item in the menu.
84970      */
84971     toggleSelected: function() {
84972         var me = this,
84973             m = me.menu,
84974             checkItem;
84975
84976         checkItem = me.activeItem.next(':not([disabled])') || m.items.getAt(0);
84977         checkItem.setChecked(true);
84978     }
84979 });
84980 /**
84981  * @class Ext.container.ButtonGroup
84982  * @extends Ext.panel.Panel
84983  * <p>Provides a container for arranging a group of related Buttons in a tabular manner.</p>
84984  * Example usage:
84985  * {@img Ext.container.ButtonGroup/Ext.container.ButtonGroup.png Ext.container.ButtonGroup component}
84986  * <pre><code>
84987     Ext.create('Ext.panel.Panel', {
84988         title: 'Panel with ButtonGroup',
84989         width: 300,
84990         height:200,
84991         renderTo: document.body,
84992         html: 'HTML Panel Content',
84993         tbar: [{
84994             xtype: 'buttongroup',
84995             columns: 3,
84996             title: 'Clipboard',
84997             items: [{
84998                 text: 'Paste',
84999                 scale: 'large',
85000                 rowspan: 3,
85001                 iconCls: 'add',
85002                 iconAlign: 'top',
85003                 cls: 'x-btn-as-arrow'
85004             },{
85005                 xtype:'splitbutton',
85006                 text: 'Menu Button',
85007                 scale: 'large',
85008                 rowspan: 3,
85009                 iconCls: 'add',
85010                 iconAlign: 'top',
85011                 arrowAlign:'bottom',
85012                 menu: [{text: 'Menu Item 1'}]
85013             },{
85014                 xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
85015             },{
85016                 text: 'Copy', iconCls: 'add16'
85017             },{
85018                 text: 'Format', iconCls: 'add16'
85019             }]
85020         }]
85021     });
85022  * </code></pre>
85023  * @constructor
85024  * Create a new ButtonGroup.
85025  * @param {Object} config The config object
85026  * @xtype buttongroup
85027  */
85028 Ext.define('Ext.container.ButtonGroup', {
85029     extend: 'Ext.panel.Panel',
85030     alias: 'widget.buttongroup',
85031     alternateClassName: 'Ext.ButtonGroup',
85032
85033     /**
85034      * @cfg {Number} columns The <tt>columns</tt> configuration property passed to the
85035      * {@link #layout configured layout manager}. See {@link Ext.layout.container.Table#columns}.
85036      */
85037
85038     /**
85039      * @cfg {String} baseCls  Defaults to <tt>'x-btn-group'</tt>.  See {@link Ext.panel.Panel#baseCls}.
85040      */
85041     baseCls: Ext.baseCSSPrefix + 'btn-group',
85042
85043     /**
85044      * @cfg {Object} layout  Defaults to <tt>'table'</tt>.  See {@link Ext.container.Container#layout}.
85045      */
85046     layout: {
85047         type: 'table'
85048     },
85049
85050     defaultType: 'button',
85051
85052     /**
85053      * @cfg {Boolean} frame  Defaults to <tt>true</tt>.  See {@link Ext.panel.Panel#frame}.
85054      */
85055     frame: true,
85056     
85057     frameHeader: false,
85058     
85059     internalDefaults: {removeMode: 'container', hideParent: true},
85060
85061     initComponent : function(){
85062         // Copy the component's columns config to the layout if specified
85063         var me = this,
85064             cols = me.columns;
85065
85066         me.noTitleCls = me.baseCls + '-notitle';
85067         if (cols) {
85068             me.layout = Ext.apply({}, {columns: cols}, me.layout);
85069         }
85070
85071         if (!me.title) {
85072             me.addCls(me.noTitleCls);
85073         }
85074         me.callParent(arguments);
85075     },
85076
85077     afterLayout: function() {
85078         var me = this;
85079         
85080         me.callParent(arguments);
85081
85082         // Pugly hack for a pugly browser:
85083         // If not an explicitly set width, then size the width to match the inner table
85084         if (me.layout.table && (Ext.isIEQuirks || Ext.isIE6) && !me.width) {
85085             var t = me.getTargetEl();
85086             t.setWidth(me.layout.table.offsetWidth + t.getPadding('lr'));
85087         }
85088     },
85089
85090     afterRender: function() {
85091         var me = this;
85092         
85093         //we need to add an addition item in here so the ButtonGroup title is centered
85094         if (me.header) {
85095             me.header.insert(0, {
85096                 xtype: 'component',
85097                 ui   : me.ui,
85098                 html : '&nbsp;',
85099                 flex : 1
85100             });
85101         }
85102         
85103         me.callParent(arguments);
85104     },
85105     
85106     // private
85107     onBeforeAdd: function(component) {
85108         if (component.is('button')) {
85109             component.ui = component.ui + '-toolbar';
85110         }
85111         this.callParent(arguments);
85112     },
85113
85114     //private
85115     applyDefaults: function(c) {
85116         if (!Ext.isString(c)) {
85117             c = this.callParent(arguments);
85118             var d = this.internalDefaults;
85119             if (c.events) {
85120                 Ext.applyIf(c.initialConfig, d);
85121                 Ext.apply(c, d);
85122             } else {
85123                 Ext.applyIf(c, d);
85124             }
85125         }
85126         return c;
85127     }
85128
85129     /**
85130      * @cfg {Array} tools  @hide
85131      */
85132     /**
85133      * @cfg {Boolean} collapsible  @hide
85134      */
85135     /**
85136      * @cfg {Boolean} collapseMode  @hide
85137      */
85138     /**
85139      * @cfg {Boolean} animCollapse  @hide
85140      */
85141     /**
85142      * @cfg {Boolean} closable  @hide
85143      */
85144 });
85145
85146 /**
85147  * @class Ext.container.Viewport
85148  * @extends Ext.container.Container
85149
85150 A specialized container representing the viewable application area (the browser viewport).
85151
85152 The Viewport renders itself to the document body, and automatically sizes itself to the size of
85153 the browser viewport and manages window resizing. There may only be one Viewport created
85154 in a page.
85155
85156 Like any {@link Ext.container.Container Container}, a Viewport will only perform sizing and positioning
85157 on its child Components if you configure it with a {@link #layout}.
85158
85159 A Common layout used with Viewports is {@link Ext.layout.container.Border border layout}, but if the
85160 required layout is simpler, a different layout should be chosen.
85161
85162 For example, to simply make a single child item occupy all available space, use {@link Ext.layout.container.Fit fit layout}.
85163
85164 To display one "active" item at full size from a choice of several child items, use {@link Ext.layout.container.Card card layout}.
85165
85166 Inner layouts are available by virtue of the fact that all {@link Ext.panel.Panel Panel}s
85167 added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
85168 method of any of its child Panels may themselves have a layout.
85169
85170 The Viewport does not provide scrolling, so child Panels within the Viewport should provide
85171 for scrolling if needed using the {@link #autoScroll} config.
85172 {@img Ext.container.Viewport/Ext.container.Viewport.png Ext.container.Viewport component}
85173 An example showing a classic application border layout:
85174
85175     Ext.create('Ext.container.Viewport', {
85176         layout: 'border',
85177         renderTo: Ext.getBody(),
85178         items: [{
85179             region: 'north',
85180             html: '<h1 class="x-panel-header">Page Title</h1>',
85181             autoHeight: true,
85182             border: false,
85183             margins: '0 0 5 0'
85184         }, {
85185             region: 'west',
85186             collapsible: true,
85187             title: 'Navigation',
85188             width: 150
85189             // could use a TreePanel or AccordionLayout for navigational items
85190         }, {
85191             region: 'south',
85192             title: 'South Panel',
85193             collapsible: true,
85194             html: 'Information goes here',
85195             split: true,
85196             height: 100,
85197             minHeight: 100
85198         }, {
85199             region: 'east',
85200             title: 'East Panel',
85201             collapsible: true,
85202             split: true,
85203             width: 150
85204         }, {
85205             region: 'center',
85206             xtype: 'tabpanel', // TabPanel itself has no title
85207             activeTab: 0,      // First tab active by default
85208             items: {
85209                 title: 'Default Tab',
85210                 html: 'The first tab\'s content. Others may be added dynamically'
85211             }
85212         }]
85213     });
85214
85215  * @constructor
85216  * Create a new Viewport
85217  * @param {Object} config The config object
85218  * @markdown
85219  * @xtype viewport
85220  */
85221 Ext.define('Ext.container.Viewport', {
85222     extend: 'Ext.container.Container',
85223     alias: 'widget.viewport',
85224     requires: ['Ext.EventManager'],
85225     alternateClassName: 'Ext.Viewport',
85226
85227     /*
85228      * Privatize config options which, if used, would interfere with the
85229      * correct operation of the Viewport as the sole manager of the
85230      * layout of the document body.
85231      */
85232     /**
85233      * @cfg {Mixed} applyTo @hide
85234      */
85235     /**
85236      * @cfg {Boolean} allowDomMove @hide
85237      */
85238     /**
85239      * @cfg {Boolean} hideParent @hide
85240      */
85241     /**
85242      * @cfg {Mixed} renderTo @hide
85243      */
85244     /**
85245      * @cfg {Boolean} hideParent @hide
85246      */
85247     /**
85248      * @cfg {Number} height @hide
85249      */
85250     /**
85251      * @cfg {Number} width @hide
85252      */
85253     /**
85254      * @cfg {Boolean} autoHeight @hide
85255      */
85256     /**
85257      * @cfg {Boolean} autoWidth @hide
85258      */
85259     /**
85260      * @cfg {Boolean} deferHeight @hide
85261      */
85262     /**
85263      * @cfg {Boolean} monitorResize @hide
85264      */
85265
85266     isViewport: true,
85267
85268     ariaRole: 'application',
85269     initComponent : function() {
85270         var me = this,
85271             html = Ext.fly(document.body.parentNode),
85272             el;
85273         me.callParent(arguments);
85274         html.addCls(Ext.baseCSSPrefix + 'viewport');
85275         if (me.autoScroll) {
85276             html.setStyle('overflow', 'auto');
85277         }
85278         me.el = el = Ext.getBody();
85279         el.setHeight = Ext.emptyFn;
85280         el.setWidth = Ext.emptyFn;
85281         el.setSize = Ext.emptyFn;
85282         el.dom.scroll = 'no';
85283         me.allowDomMove = false;
85284         //this.autoWidth = true;
85285         //this.autoHeight = true;
85286         Ext.EventManager.onWindowResize(me.fireResize, me);
85287         me.renderTo = me.el;
85288     },
85289
85290     fireResize : function(w, h){
85291         // setSize is the single entry point to layouts
85292         this.setSize(w, h);
85293         //this.fireEvent('resize', this, w, h, w, h);
85294     }
85295 });
85296
85297 /*
85298  * This is a derivative of the similarly named class in the YUI Library.
85299  * The original license:
85300  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
85301  * Code licensed under the BSD License:
85302  * http://developer.yahoo.net/yui/license.txt
85303  */
85304
85305
85306 /**
85307  * @class Ext.dd.DDTarget
85308  * A DragDrop implementation that does not move, but can be a drop
85309  * target.  You would get the same result by simply omitting implementation
85310  * for the event callbacks, but this way we reduce the processing cost of the
85311  * event listener and the callbacks.
85312  * @extends Ext.dd.DragDrop
85313  * @constructor
85314  * @param {String} id the id of the element that is a drop target
85315  * @param {String} sGroup the group of related DragDrop objects
85316  * @param {object} config an object containing configurable attributes
85317  *                 Valid properties for DDTarget in addition to those in
85318  *                 DragDrop:
85319  *                    none
85320  */
85321 Ext.define('Ext.dd.DDTarget', {
85322     extend: 'Ext.dd.DragDrop',
85323     constructor: function(id, sGroup, config) {
85324         if (id) {
85325             this.initTarget(id, sGroup, config);
85326         }
85327     },
85328
85329     /**
85330      * @hide
85331      * Overridden and disabled. A DDTarget does not support being dragged.
85332      * @method
85333      */
85334     getDragEl: Ext.emptyFn,
85335     /**
85336      * @hide
85337      * Overridden and disabled. A DDTarget does not support being dragged.
85338      * @method
85339      */
85340     isValidHandleChild: Ext.emptyFn,
85341     /**
85342      * @hide
85343      * Overridden and disabled. A DDTarget does not support being dragged.
85344      * @method
85345      */
85346     startDrag: Ext.emptyFn,
85347     /**
85348      * @hide
85349      * Overridden and disabled. A DDTarget does not support being dragged.
85350      * @method
85351      */
85352     endDrag: Ext.emptyFn,
85353     /**
85354      * @hide
85355      * Overridden and disabled. A DDTarget does not support being dragged.
85356      * @method
85357      */
85358     onDrag: Ext.emptyFn,
85359     /**
85360      * @hide
85361      * Overridden and disabled. A DDTarget does not support being dragged.
85362      * @method
85363      */
85364     onDragDrop: Ext.emptyFn,
85365     /**
85366      * @hide
85367      * Overridden and disabled. A DDTarget does not support being dragged.
85368      * @method
85369      */
85370     onDragEnter: Ext.emptyFn,
85371     /**
85372      * @hide
85373      * Overridden and disabled. A DDTarget does not support being dragged.
85374      * @method
85375      */
85376     onDragOut: Ext.emptyFn,
85377     /**
85378      * @hide
85379      * Overridden and disabled. A DDTarget does not support being dragged.
85380      * @method
85381      */
85382     onDragOver: Ext.emptyFn,
85383     /**
85384      * @hide
85385      * Overridden and disabled. A DDTarget does not support being dragged.
85386      * @method
85387      */
85388     onInvalidDrop: Ext.emptyFn,
85389     /**
85390      * @hide
85391      * Overridden and disabled. A DDTarget does not support being dragged.
85392      * @method
85393      */
85394     onMouseDown: Ext.emptyFn,
85395     /**
85396      * @hide
85397      * Overridden and disabled. A DDTarget does not support being dragged.
85398      * @method
85399      */
85400     onMouseUp: Ext.emptyFn,
85401     /**
85402      * @hide
85403      * Overridden and disabled. A DDTarget does not support being dragged.
85404      * @method
85405      */
85406     setXConstraint: Ext.emptyFn,
85407     /**
85408      * @hide
85409      * Overridden and disabled. A DDTarget does not support being dragged.
85410      * @method
85411      */
85412     setYConstraint: Ext.emptyFn,
85413     /**
85414      * @hide
85415      * Overridden and disabled. A DDTarget does not support being dragged.
85416      * @method
85417      */
85418     resetConstraints: Ext.emptyFn,
85419     /**
85420      * @hide
85421      * Overridden and disabled. A DDTarget does not support being dragged.
85422      * @method
85423      */
85424     clearConstraints: Ext.emptyFn,
85425     /**
85426      * @hide
85427      * Overridden and disabled. A DDTarget does not support being dragged.
85428      * @method
85429      */
85430     clearTicks: Ext.emptyFn,
85431     /**
85432      * @hide
85433      * Overridden and disabled. A DDTarget does not support being dragged.
85434      * @method
85435      */
85436     setInitPosition: Ext.emptyFn,
85437     /**
85438      * @hide
85439      * Overridden and disabled. A DDTarget does not support being dragged.
85440      * @method
85441      */
85442     setDragElId: Ext.emptyFn,
85443     /**
85444      * @hide
85445      * Overridden and disabled. A DDTarget does not support being dragged.
85446      * @method
85447      */
85448     setHandleElId: Ext.emptyFn,
85449     /**
85450      * @hide
85451      * Overridden and disabled. A DDTarget does not support being dragged.
85452      * @method
85453      */
85454     setOuterHandleElId: Ext.emptyFn,
85455     /**
85456      * @hide
85457      * Overridden and disabled. A DDTarget does not support being dragged.
85458      * @method
85459      */
85460     addInvalidHandleClass: Ext.emptyFn,
85461     /**
85462      * @hide
85463      * Overridden and disabled. A DDTarget does not support being dragged.
85464      * @method
85465      */
85466     addInvalidHandleId: Ext.emptyFn,
85467     /**
85468      * @hide
85469      * Overridden and disabled. A DDTarget does not support being dragged.
85470      * @method
85471      */
85472     addInvalidHandleType: Ext.emptyFn,
85473     /**
85474      * @hide
85475      * Overridden and disabled. A DDTarget does not support being dragged.
85476      * @method
85477      */
85478     removeInvalidHandleClass: Ext.emptyFn,
85479     /**
85480      * @hide
85481      * Overridden and disabled. A DDTarget does not support being dragged.
85482      * @method
85483      */
85484     removeInvalidHandleId: Ext.emptyFn,
85485     /**
85486      * @hide
85487      * Overridden and disabled. A DDTarget does not support being dragged.
85488      * @method
85489      */
85490     removeInvalidHandleType: Ext.emptyFn,
85491
85492     toString: function() {
85493         return ("DDTarget " + this.id);
85494     }
85495 });
85496 /**
85497  * @class Ext.dd.DragTracker
85498  * A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
85499  * as well as during the drag. This is useful for components such as {@link Ext.slider.Multi}, where there is
85500  * an element that can be dragged around to change the Slider's value.
85501  * DragTracker provides a series of template methods that should be overridden to provide functionality
85502  * in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
85503  * See {@link Ext.slider.Multi}'s initEvents function for an example implementation.
85504  */
85505 Ext.define('Ext.dd.DragTracker', {
85506
85507     uses: ['Ext.util.Region'],
85508
85509     mixins: {
85510         observable: 'Ext.util.Observable'
85511     },
85512
85513     /**
85514      * @property active
85515      * @type Boolean
85516      * Read-only property indicated whether the user is currently dragging this
85517      * tracker.
85518      */
85519     active: false,
85520
85521     /**
85522      * @property dragTarget
85523      * @type HtmlElement
85524      * <p><b>Only valid during drag operations. Read-only.</b></p>
85525      * <p>The element being dragged.</p>
85526      * <p>If the {@link #delegate} option is used, this will be the delegate element which was mousedowned.</p>
85527      */
85528
85529     /**
85530      * @cfg {Boolean} trackOver
85531      * <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>
85532      * <p>This is implicitly set when an {@link #overCls} is specified.</p>
85533      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
85534      */
85535     trackOver: false,
85536
85537     /**
85538      * @cfg {String} overCls
85539      * <p>A CSS class to add to the DragTracker's target element when the element (or, if the {@link #delegate} option is used,
85540      * when a delegate element) is mouseovered.</p>
85541      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
85542      */
85543
85544     /**
85545      * @cfg {Ext.util.Region/Element} constrainTo
85546      * <p>A {@link Ext.util.Region Region} (Or an element from which a Region measurement will be read) which is used to constrain
85547      * the result of the {@link #getOffset} call.</p>
85548      * <p>This may be set any time during the DragTracker's lifecycle to set a dynamic constraining region.</p>
85549      */
85550
85551     /**
85552      * @cfg {Number} tolerance
85553      * Number of pixels the drag target must be moved before dragging is
85554      * considered to have started. Defaults to <code>5</code>.
85555      */
85556     tolerance: 5,
85557
85558     /**
85559      * @cfg {Boolean/Number} autoStart
85560      * Defaults to <code>false</code>. Specify <code>true</code> to defer trigger start by 1000 ms.
85561      * Specify a Number for the number of milliseconds to defer trigger start.
85562      */
85563     autoStart: false,
85564
85565     /**
85566      * @cfg {String} delegate
85567      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the DragTracker's encapsulating
85568      * Element which are the tracked elements. This limits tracking to only begin when the matching elements are mousedowned.</p>
85569      * <p>This may also be a specific child element within the DragTracker's encapsulating element to use as the tracked element.</p>
85570      */
85571
85572     /**
85573      * @cfg {Boolean} preventDefault
85574      * Specify <code>false</code> to enable default actions on onMouseDown events. Defaults to <code>true</code>.
85575      */
85576
85577     /**
85578      * @cfg {Boolean} stopEvent
85579      * 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>.
85580      */
85581
85582     constructor : function(config){
85583         Ext.apply(this, config);
85584         this.addEvents(
85585             /**
85586              * @event mouseover <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
85587              * <p>Fires when the mouse enters the DragTracker's target element (or if {@link #delegate} is
85588              * used, when the mouse enters a delegate element).</p>
85589              * @param {Object} this
85590              * @param {Object} e event object
85591              * @param {HtmlElement} target The element mouseovered.
85592              */
85593             'mouseover',
85594
85595             /**
85596              * @event mouseout <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
85597              * <p>Fires when the mouse exits the DragTracker's target element (or if {@link #delegate} is
85598              * used, when the mouse exits a delegate element).</p>
85599              * @param {Object} this
85600              * @param {Object} e event object
85601              */
85602             'mouseout',
85603
85604             /**
85605              * @event mousedown <p>Fires when the mouse button is pressed down, but before a drag operation begins. The
85606              * drag operation begins after either the mouse has been moved by {@link #tolerance} pixels, or after
85607              * the {@link #autoStart} timer fires.</p>
85608              * <p>Return false to veto the drag operation.</p>
85609              * @param {Object} this
85610              * @param {Object} e event object
85611              */
85612             'mousedown',
85613
85614             /**
85615              * @event mouseup
85616              * @param {Object} this
85617              * @param {Object} e event object
85618              */
85619             'mouseup',
85620
85621             /**
85622              * @event mousemove Fired when the mouse is moved. Returning false cancels the drag operation.
85623              * @param {Object} this
85624              * @param {Object} e event object
85625              */
85626             'mousemove',
85627
85628             /**
85629              * @event beforestart
85630              * @param {Object} this
85631              * @param {Object} e event object
85632              */
85633             'beforedragstart',
85634
85635             /**
85636              * @event dragstart
85637              * @param {Object} this
85638              * @param {Object} e event object
85639              */
85640             'dragstart',
85641
85642             /**
85643              * @event dragend
85644              * @param {Object} this
85645              * @param {Object} e event object
85646              */
85647             'dragend',
85648
85649             /**
85650              * @event drag
85651              * @param {Object} this
85652              * @param {Object} e event object
85653              */
85654             'drag'
85655         );
85656
85657         this.dragRegion = Ext.create('Ext.util.Region', 0,0,0,0);
85658
85659         if (this.el) {
85660             this.initEl(this.el);
85661         }
85662
85663         // Dont pass the config so that it is not applied to 'this' again
85664         this.mixins.observable.constructor.call(this);
85665         if (this.disabled) {
85666             this.disable();
85667         }
85668
85669     },
85670
85671     /**
85672      * Initializes the DragTracker on a given element.
85673      * @param {Ext.core.Element/HTMLElement} el The element
85674      */
85675     initEl: function(el) {
85676         this.el = Ext.get(el);
85677
85678         // The delegate option may also be an element on which to listen
85679         this.handle = Ext.get(this.delegate);
85680
85681         // If delegate specified an actual element to listen on, we do not use the delegate listener option
85682         this.delegate = this.handle ? undefined : this.delegate;
85683
85684         if (!this.handle) {
85685             this.handle = this.el;
85686         }
85687
85688         // Add a mousedown listener which reacts only on the elements targeted by the delegate config.
85689         // We process mousedown to begin tracking.
85690         this.mon(this.handle, {
85691             mousedown: this.onMouseDown,
85692             delegate: this.delegate,
85693             scope: this
85694         });
85695
85696         // If configured to do so, track mouse entry and exit into the target (or delegate).
85697         // The mouseover and mouseout CANNOT be replaced with mouseenter and mouseleave
85698         // because delegate cannot work with those pseudoevents. Entry/exit checking is done in the handler.
85699         if (this.trackOver || this.overCls) {
85700             this.mon(this.handle, {
85701                 mouseover: this.onMouseOver,
85702                 mouseout: this.onMouseOut,
85703                 delegate: this.delegate,
85704                 scope: this
85705             });
85706         }
85707     },
85708
85709     disable: function() {
85710         this.disabled = true;
85711     },
85712
85713     enable: function() {
85714         this.disabled = false;
85715     },
85716
85717     destroy : function() {
85718         this.clearListeners();
85719         delete this.el;
85720     },
85721
85722     // When the pointer enters a tracking element, fire a mouseover if the mouse entered from outside.
85723     // This is mouseenter functionality, but we cannot use mouseenter because we are using "delegate" to filter mouse targets
85724     onMouseOver: function(e, target) {
85725         var me = this;
85726         if (!me.disabled) {
85727             if (Ext.EventManager.contains(e) || me.delegate) {
85728                 me.mouseIsOut = false;
85729                 if (me.overCls) {
85730                     me.el.addCls(me.overCls);
85731                 }
85732                 me.fireEvent('mouseover', me, e, me.delegate ? e.getTarget(me.delegate, target) : me.handle);
85733             }
85734         }
85735     },
85736
85737     // When the pointer exits a tracking element, fire a mouseout.
85738     // This is mouseleave functionality, but we cannot use mouseleave because we are using "delegate" to filter mouse targets
85739     onMouseOut: function(e) {
85740         if (this.mouseIsDown) {
85741             this.mouseIsOut = true;
85742         } else {
85743             if (this.overCls) {
85744                 this.el.removeCls(this.overCls);
85745             }
85746             this.fireEvent('mouseout', this, e);
85747         }
85748     },
85749
85750     onMouseDown: function(e, target){
85751         // If this is disabled, or the mousedown has been processed by an upstream DragTracker, return
85752         if (this.disabled ||e.dragTracked) {
85753             return;
85754         }
85755
85756         // This information should be available in mousedown listener and onBeforeStart implementations
85757         this.dragTarget = this.delegate ? target : this.handle.dom;
85758         this.startXY = this.lastXY = e.getXY();
85759         this.startRegion = Ext.fly(this.dragTarget).getRegion();
85760
85761         if (this.fireEvent('mousedown', this, e) === false ||
85762             this.fireEvent('beforedragstart', this, e) === false ||
85763             this.onBeforeStart(e) === false) {
85764             return;
85765         }
85766
85767         // Track when the mouse is down so that mouseouts while the mouse is down are not processed.
85768         // The onMouseOut method will only ever be called after mouseup.
85769         this.mouseIsDown = true;
85770
85771         // Flag for downstream DragTracker instances that the mouse is being tracked.
85772         e.dragTracked = true;
85773
85774         if (this.preventDefault !== false) {
85775             e.preventDefault();
85776         }
85777         Ext.getDoc().on({
85778             scope: this,
85779             mouseup: this.onMouseUp,
85780             mousemove: this.onMouseMove,
85781             selectstart: this.stopSelect
85782         });
85783         if (this.autoStart) {
85784             this.timer =  Ext.defer(this.triggerStart, this.autoStart === true ? 1000 : this.autoStart, this, [e]);
85785         }
85786     },
85787
85788     onMouseMove: function(e, target){
85789         // BrowserBug: IE hack to see if button was released outside of window.
85790         // Needed in IE6-9 in quirks and strictmode
85791         if (this.active && Ext.isIE && !e.browserEvent.button) {
85792             e.preventDefault();
85793             this.onMouseUp(e);
85794             return;
85795         }
85796
85797         e.preventDefault();
85798         var xy = e.getXY(),
85799             s = this.startXY;
85800
85801         this.lastXY = xy;
85802         if (!this.active) {
85803             if (Math.max(Math.abs(s[0]-xy[0]), Math.abs(s[1]-xy[1])) > this.tolerance) {
85804                 this.triggerStart(e);
85805             } else {
85806                 return;
85807             }
85808         }
85809
85810         // Returning false from a mousemove listener deactivates 
85811         if (this.fireEvent('mousemove', this, e) === false) {
85812             this.onMouseUp(e);
85813         } else {
85814             this.onDrag(e);
85815             this.fireEvent('drag', this, e);
85816         }
85817     },
85818
85819     onMouseUp: function(e) {
85820         // Clear the flag which ensures onMouseOut fires only after the mouse button
85821         // is lifted if the mouseout happens *during* a drag.
85822         this.mouseIsDown = false;
85823
85824         // Remove flag from event singleton
85825         delete e.dragTracked;
85826
85827         // If we mouseouted the el *during* the drag, the onMouseOut method will not have fired. Ensure that it gets processed.
85828         if (this.mouseIsOut) {
85829             this.mouseIsOut = false;
85830             this.onMouseOut(e);
85831         }
85832         e.preventDefault();
85833         this.fireEvent('mouseup', this, e);
85834         this.endDrag(e);
85835     },
85836
85837     /**
85838      * @private
85839      * Stop the drag operation, and remove active mouse listeners.
85840      */
85841     endDrag: function(e) {
85842         var doc = Ext.getDoc(),
85843         wasActive = this.active;
85844
85845         doc.un('mousemove', this.onMouseMove, this);
85846         doc.un('mouseup', this.onMouseUp, this);
85847         doc.un('selectstart', this.stopSelect, this);
85848         this.clearStart();
85849         this.active = false;
85850         if (wasActive) {
85851             this.onEnd(e);
85852             this.fireEvent('dragend', this, e);
85853         }
85854         // Private property calculated when first required and only cached during a drag
85855         delete this._constrainRegion;
85856     },
85857
85858     triggerStart: function(e) {
85859         this.clearStart();
85860         this.active = true;
85861         this.onStart(e);
85862         this.fireEvent('dragstart', this, e);
85863     },
85864
85865     clearStart : function() {
85866         if (this.timer) {
85867             clearTimeout(this.timer);
85868             delete this.timer;
85869         }
85870     },
85871
85872     stopSelect : function(e) {
85873         e.stopEvent();
85874         return false;
85875     },
85876
85877     /**
85878      * Template method which should be overridden by each DragTracker instance. Called when the user first clicks and
85879      * holds the mouse button down. Return false to disallow the drag
85880      * @param {Ext.EventObject} e The event object
85881      */
85882     onBeforeStart : function(e) {
85883
85884     },
85885
85886     /**
85887      * Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
85888      * (e.g. the user has moved the tracked element beyond the specified tolerance)
85889      * @param {Ext.EventObject} e The event object
85890      */
85891     onStart : function(xy) {
85892
85893     },
85894
85895     /**
85896      * Template method which should be overridden by each DragTracker instance. Called whenever a drag has been detected.
85897      * @param {Ext.EventObject} e The event object
85898      */
85899     onDrag : function(e) {
85900
85901     },
85902
85903     /**
85904      * Template method which should be overridden by each DragTracker instance. Called when a drag operation has been completed
85905      * (e.g. the user clicked and held the mouse down, dragged the element and then released the mouse button)
85906      * @param {Ext.EventObject} e The event object
85907      */
85908     onEnd : function(e) {
85909
85910     },
85911
85912     /**
85913      * </p>Returns the drag target. This is usually the DragTracker's encapsulating element.</p>
85914      * <p>If the {@link #delegate} option is being used, this may be a child element which matches the
85915      * {@link #delegate} selector.</p>
85916      * @return {Ext.core.Element} The element currently being tracked.
85917      */
85918     getDragTarget : function(){
85919         return this.dragTarget;
85920     },
85921
85922     /**
85923      * @private
85924      * @returns {Element} The DragTracker's encapsulating element.
85925      */
85926     getDragCt : function(){
85927         return this.el;
85928     },
85929
85930     /**
85931      * @private
85932      * Return the Region into which the drag operation is constrained.
85933      * Either the XY pointer itself can be constrained, or the dragTarget element
85934      * The private property _constrainRegion is cached until onMouseUp
85935      */
85936     getConstrainRegion: function() {
85937         if (this.constrainTo) {
85938             if (this.constrainTo instanceof Ext.util.Region) {
85939                 return this.constrainTo;
85940             }
85941             if (!this._constrainRegion) {
85942                 this._constrainRegion = Ext.fly(this.constrainTo).getViewRegion();
85943             }
85944         } else {
85945             if (!this._constrainRegion) {
85946                 this._constrainRegion = this.getDragCt().getViewRegion();
85947             }
85948         }
85949         return this._constrainRegion;
85950     },
85951
85952     getXY : function(constrain){
85953         return constrain ? this.constrainModes[constrain](this, this.lastXY) : this.lastXY;
85954     },
85955
85956     /**
85957      * <p>Returns the X, Y offset of the current mouse position from the mousedown point.</p>
85958      * <p>This method may optionally constrain the real offset values, and returns a point coerced in one
85959      * of two modes:</p><ul>
85960      * <li><code>point</code><div class="sub-desc">The current mouse position is coerced into the
85961      * {@link #constrainRegion}, and the resulting position is returned.</div></li>
85962      * <li><code>dragTarget</code><div class="sub-desc">The new {@link Ext.util.Region Region} of the
85963      * {@link #getDragTarget dragTarget} is calculated based upon the current mouse position, and then
85964      * coerced into the {@link #constrainRegion}. The returned mouse position is then adjusted by the
85965      * same delta as was used to coerce the region.</div></li>
85966      * </ul>
85967      * @param constrainMode {String} Optional. If omitted the true mouse position is returned. May be passed
85968      * as <code>'point'</code> or <code>'dragTarget'. See above.</code>.
85969      * @returns {Array} The <code>X, Y</code> offset from the mousedown point, optionally constrained.
85970      */
85971     getOffset : function(constrain){
85972         var xy = this.getXY(constrain),
85973             s = this.startXY;
85974
85975         return [xy[0]-s[0], xy[1]-s[1]];
85976     },
85977
85978     constrainModes: {
85979         // Constrain the passed point to within the constrain region
85980         point: function(me, xy) {
85981             var dr = me.dragRegion,
85982                 constrainTo = me.getConstrainRegion();
85983
85984             // No constraint
85985             if (!constrainTo) {
85986                 return xy;
85987             }
85988
85989             dr.x = dr.left = dr[0] = dr.right = xy[0];
85990             dr.y = dr.top = dr[1] = dr.bottom = xy[1];
85991             dr.constrainTo(constrainTo);
85992
85993             return [dr.left, dr.top];
85994         },
85995
85996         // Constrain the dragTarget to within the constrain region. Return the passed xy adjusted by the same delta.
85997         dragTarget: function(me, xy) {
85998             var s = me.startXY,
85999                 dr = me.startRegion.copy(),
86000                 constrainTo = me.getConstrainRegion(),
86001                 adjust;
86002
86003             // No constraint
86004             if (!constrainTo) {
86005                 return xy;
86006             }
86007
86008             // See where the passed XY would put the dragTarget if translated by the unconstrained offset.
86009             // If it overflows, we constrain the passed XY to bring the potential
86010             // region back within the boundary.
86011             dr.translateBy(xy[0]-s[0], xy[1]-s[1]);
86012
86013             // Constrain the X coordinate by however much the dragTarget overflows
86014             if (dr.right > constrainTo.right) {
86015                 xy[0] += adjust = (constrainTo.right - dr.right);    // overflowed the right
86016                 dr.left += adjust;
86017             }
86018             if (dr.left < constrainTo.left) {
86019                 xy[0] += (constrainTo.left - dr.left);      // overflowed the left
86020             }
86021
86022             // Constrain the Y coordinate by however much the dragTarget overflows
86023             if (dr.bottom > constrainTo.bottom) {
86024                 xy[1] += adjust = (constrainTo.bottom - dr.bottom);  // overflowed the bottom
86025                 dr.top += adjust;
86026             }
86027             if (dr.top < constrainTo.top) {
86028                 xy[1] += (constrainTo.top - dr.top);        // overflowed the top
86029             }
86030             return xy;
86031         }
86032     }
86033 });
86034 /**
86035  * @class Ext.dd.DragZone
86036  * @extends Ext.dd.DragSource
86037  * <p>This class provides a container DD instance that allows dragging of multiple child source nodes.</p>
86038  * <p>This class does not move the drag target nodes, but a proxy element which may contain
86039  * any DOM structure you wish. The DOM element to show in the proxy is provided by either a
86040  * provided implementation of {@link #getDragData}, or by registered draggables registered with {@link Ext.dd.Registry}</p>
86041  * <p>If you wish to provide draggability for an arbitrary number of DOM nodes, each of which represent some
86042  * application object (For example nodes in a {@link Ext.view.View DataView}) then use of this class
86043  * is the most efficient way to "activate" those nodes.</p>
86044  * <p>By default, this class requires that draggable child nodes are registered with {@link Ext.dd.Registry}.
86045  * However a simpler way to allow a DragZone to manage any number of draggable elements is to configure
86046  * the DragZone with  an implementation of the {@link #getDragData} method which interrogates the passed
86047  * mouse event to see if it has taken place within an element, or class of elements. This is easily done
86048  * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
86049  * {@link Ext.DomQuery} selector. For example, to make the nodes of a DataView draggable, use the following
86050  * technique. Knowledge of the use of the DataView is required:</p><pre><code>
86051 myDataView.on('render', function(v) {
86052     myDataView.dragZone = new Ext.dd.DragZone(v.getEl(), {
86053
86054 //      On receipt of a mousedown event, see if it is within a DataView node.
86055 //      Return a drag data object if so.
86056         getDragData: function(e) {
86057
86058 //          Use the DataView's own itemSelector (a mandatory property) to
86059 //          test if the mousedown is within one of the DataView's nodes.
86060             var sourceEl = e.getTarget(v.itemSelector, 10);
86061
86062 //          If the mousedown is within a DataView node, clone the node to produce
86063 //          a ddel element for use by the drag proxy. Also add application data
86064 //          to the returned data object.
86065             if (sourceEl) {
86066                 d = sourceEl.cloneNode(true);
86067                 d.id = Ext.id();
86068                 return {
86069                     ddel: d,
86070                     sourceEl: sourceEl,
86071                     repairXY: Ext.fly(sourceEl).getXY(),
86072                     sourceStore: v.store,
86073                     draggedRecord: v.{@link Ext.view.View#getRecord getRecord}(sourceEl)
86074                 }
86075             }
86076         },
86077
86078 //      Provide coordinates for the proxy to slide back to on failed drag.
86079 //      This is the original XY coordinates of the draggable element captured
86080 //      in the getDragData method.
86081         getRepairXY: function() {
86082             return this.dragData.repairXY;
86083         }
86084     });
86085 });</code></pre>
86086  * See the {@link Ext.dd.DropZone DropZone} documentation for details about building a DropZone which
86087  * cooperates with this DragZone.
86088  * @constructor
86089  * @param {Mixed} el The container element
86090  * @param {Object} config
86091  */
86092 Ext.define('Ext.dd.DragZone', {
86093
86094     extend: 'Ext.dd.DragSource',
86095
86096     constructor : function(el, config){
86097         this.callParent([el, config]);
86098         if (this.containerScroll) {
86099             Ext.dd.ScrollManager.register(this.el);
86100         }
86101     },
86102
86103     /**
86104      * This property contains the data representing the dragged object. This data is set up by the implementation
86105      * of the {@link #getDragData} method. It must contain a <tt>ddel</tt> property, but can contain
86106      * any other data according to the application's needs.
86107      * @type Object
86108      * @property dragData
86109      */
86110
86111     /**
86112      * @cfg {Boolean} containerScroll True to register this container with the Scrollmanager
86113      * for auto scrolling during drag operations.
86114      */
86115
86116     /**
86117      * Called when a mousedown occurs in this container. Looks in {@link Ext.dd.Registry}
86118      * for a valid target to drag based on the mouse down. Override this method
86119      * to provide your own lookup logic (e.g. finding a child by class name). Make sure your returned
86120      * object has a "ddel" attribute (with an HTML Element) for other functions to work.
86121      * @param {EventObject} e The mouse down event
86122      * @return {Object} The dragData
86123      */
86124     getDragData : function(e){
86125         return Ext.dd.Registry.getHandleFromEvent(e);
86126     },
86127
86128     /**
86129      * Called once drag threshold has been reached to initialize the proxy element. By default, it clones the
86130      * this.dragData.ddel
86131      * @param {Number} x The x position of the click on the dragged object
86132      * @param {Number} y The y position of the click on the dragged object
86133      * @return {Boolean} true to continue the drag, false to cancel
86134      */
86135     onInitDrag : function(x, y){
86136         this.proxy.update(this.dragData.ddel.cloneNode(true));
86137         this.onStartDrag(x, y);
86138         return true;
86139     },
86140
86141     /**
86142      * Called after a repair of an invalid drop. By default, highlights this.dragData.ddel
86143      */
86144     afterRepair : function(){
86145         var me = this;
86146         if (Ext.enableFx) {
86147             Ext.fly(me.dragData.ddel).highlight(me.repairHighlightColor);
86148         }
86149         me.dragging = false;
86150     },
86151
86152     /**
86153      * Called before a repair of an invalid drop to get the XY to animate to. By default returns
86154      * the XY of this.dragData.ddel
86155      * @param {EventObject} e The mouse up event
86156      * @return {Array} The xy location (e.g. [100, 200])
86157      */
86158     getRepairXY : function(e){
86159         return Ext.core.Element.fly(this.dragData.ddel).getXY();
86160     },
86161
86162     destroy : function(){
86163         this.callParent();
86164         if (this.containerScroll) {
86165             Ext.dd.ScrollManager.unregister(this.el);
86166         }
86167     }
86168 });
86169
86170 /**
86171  * @class Ext.dd.ScrollManager
86172  * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
86173  * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
86174  * but you can also override most of the configs per scroll container by adding a
86175  * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
86176  * {@link #vthresh}, {@link #increment} and {@link #frequency}.  Example usage:
86177  * <pre><code>
86178 var el = Ext.get('scroll-ct');
86179 el.ddScrollConfig = {
86180     vthresh: 50,
86181     hthresh: -1,
86182     frequency: 100,
86183     increment: 200
86184 };
86185 Ext.dd.ScrollManager.register(el);
86186 </code></pre>
86187  * <b>Note: This class uses "Point Mode" and is untested in "Intersect Mode".</b>
86188  * @singleton
86189  */
86190 Ext.define('Ext.dd.ScrollManager', {
86191     singleton: true,
86192     requires: [
86193         'Ext.dd.DragDropManager'
86194     ],
86195
86196     constructor: function() {
86197         var ddm = Ext.dd.DragDropManager;
86198         ddm.fireEvents = Ext.Function.createSequence(ddm.fireEvents, this.onFire, this);
86199         ddm.stopDrag = Ext.Function.createSequence(ddm.stopDrag, this.onStop, this);
86200         this.doScroll = Ext.Function.bind(this.doScroll, this);
86201         this.ddmInstance = ddm;
86202         this.els = {};
86203         this.dragEl = null;
86204         this.proc = {};
86205     },
86206
86207     onStop: function(e){
86208         var sm = Ext.dd.ScrollManager;
86209         sm.dragEl = null;
86210         sm.clearProc();
86211     },
86212
86213     triggerRefresh: function() {
86214         if (this.ddmInstance.dragCurrent) {
86215             this.ddmInstance.refreshCache(this.ddmInstance.dragCurrent.groups);
86216         }
86217     },
86218
86219     doScroll: function() {
86220         if (this.ddmInstance.dragCurrent) {
86221             var proc   = this.proc,
86222                 procEl = proc.el,
86223                 ddScrollConfig = proc.el.ddScrollConfig,
86224                 inc = ddScrollConfig ? ddScrollConfig.increment : this.increment;
86225
86226             if (!this.animate) {
86227                 if (procEl.scroll(proc.dir, inc)) {
86228                     this.triggerRefresh();
86229                 }
86230             } else {
86231                 procEl.scroll(proc.dir, inc, true, this.animDuration, this.triggerRefresh);
86232             }
86233         }
86234     },
86235
86236     clearProc: function() {
86237         var proc = this.proc;
86238         if (proc.id) {
86239             clearInterval(proc.id);
86240         }
86241         proc.id = 0;
86242         proc.el = null;
86243         proc.dir = "";
86244     },
86245
86246     startProc: function(el, dir) {
86247         this.clearProc();
86248         this.proc.el = el;
86249         this.proc.dir = dir;
86250         var group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup : undefined,
86251             freq  = (el.ddScrollConfig && el.ddScrollConfig.frequency)
86252                   ? el.ddScrollConfig.frequency
86253                   : this.frequency;
86254
86255         if (group === undefined || this.ddmInstance.dragCurrent.ddGroup == group) {
86256             this.proc.id = setInterval(this.doScroll, freq);
86257         }
86258     },
86259
86260     onFire: function(e, isDrop) {
86261         if (isDrop || !this.ddmInstance.dragCurrent) {
86262             return;
86263         }
86264         if (!this.dragEl || this.dragEl != this.ddmInstance.dragCurrent) {
86265             this.dragEl = this.ddmInstance.dragCurrent;
86266             // refresh regions on drag start
86267             this.refreshCache();
86268         }
86269
86270         var xy = e.getXY(),
86271             pt = e.getPoint(),
86272             proc = this.proc,
86273             els = this.els;
86274
86275         for (var id in els) {
86276             var el = els[id], r = el._region;
86277             var c = el.ddScrollConfig ? el.ddScrollConfig : this;
86278             if (r && r.contains(pt) && el.isScrollable()) {
86279                 if (r.bottom - pt.y <= c.vthresh) {
86280                     if(proc.el != el){
86281                         this.startProc(el, "down");
86282                     }
86283                     return;
86284                 }else if (r.right - pt.x <= c.hthresh) {
86285                     if (proc.el != el) {
86286                         this.startProc(el, "left");
86287                     }
86288                     return;
86289                 } else if(pt.y - r.top <= c.vthresh) {
86290                     if (proc.el != el) {
86291                         this.startProc(el, "up");
86292                     }
86293                     return;
86294                 } else if(pt.x - r.left <= c.hthresh) {
86295                     if (proc.el != el) {
86296                         this.startProc(el, "right");
86297                     }
86298                     return;
86299                 }
86300             }
86301         }
86302         this.clearProc();
86303     },
86304
86305     /**
86306      * Registers new overflow element(s) to auto scroll
86307      * @param {Mixed/Array} el The id of or the element to be scrolled or an array of either
86308      */
86309     register : function(el){
86310         if (Ext.isArray(el)) {
86311             for(var i = 0, len = el.length; i < len; i++) {
86312                     this.register(el[i]);
86313             }
86314         } else {
86315             el = Ext.get(el);
86316             this.els[el.id] = el;
86317         }
86318     },
86319
86320     /**
86321      * Unregisters overflow element(s) so they are no longer scrolled
86322      * @param {Mixed/Array} el The id of or the element to be removed or an array of either
86323      */
86324     unregister : function(el){
86325         if(Ext.isArray(el)){
86326             for (var i = 0, len = el.length; i < len; i++) {
86327                 this.unregister(el[i]);
86328             }
86329         }else{
86330             el = Ext.get(el);
86331             delete this.els[el.id];
86332         }
86333     },
86334
86335     /**
86336      * The number of pixels from the top or bottom edge of a container the pointer needs to be to
86337      * trigger scrolling (defaults to 25)
86338      * @type Number
86339      */
86340     vthresh : 25,
86341     /**
86342      * The number of pixels from the right or left edge of a container the pointer needs to be to
86343      * trigger scrolling (defaults to 25)
86344      * @type Number
86345      */
86346     hthresh : 25,
86347
86348     /**
86349      * The number of pixels to scroll in each scroll increment (defaults to 100)
86350      * @type Number
86351      */
86352     increment : 100,
86353
86354     /**
86355      * The frequency of scrolls in milliseconds (defaults to 500)
86356      * @type Number
86357      */
86358     frequency : 500,
86359
86360     /**
86361      * True to animate the scroll (defaults to true)
86362      * @type Boolean
86363      */
86364     animate: true,
86365
86366     /**
86367      * The animation duration in seconds -
86368      * MUST BE less than Ext.dd.ScrollManager.frequency! (defaults to .4)
86369      * @type Number
86370      */
86371     animDuration: 0.4,
86372
86373     /**
86374      * The named drag drop {@link Ext.dd.DragSource#ddGroup group} to which this container belongs (defaults to undefined).
86375      * If a ddGroup is specified, then container scrolling will only occur when a dragged object is in the same ddGroup.
86376      * @type String
86377      */
86378     ddGroup: undefined,
86379
86380     /**
86381      * Manually trigger a cache refresh.
86382      */
86383     refreshCache : function(){
86384         var els = this.els,
86385             id;
86386         for (id in els) {
86387             if(typeof els[id] == 'object'){ // for people extending the object prototype
86388                 els[id]._region = els[id].getRegion();
86389             }
86390         }
86391     }
86392 });
86393
86394 /**
86395  * @class Ext.dd.DropTarget
86396  * @extends Ext.dd.DDTarget
86397  * A simple class that provides the basic implementation needed to make any element a drop target that can have
86398  * draggable items dropped onto it.  The drop has no effect until an implementation of notifyDrop is provided.
86399  * @constructor
86400  * @param {Mixed} el The container element
86401  * @param {Object} config
86402  */
86403 Ext.define('Ext.dd.DropTarget', {
86404     extend: 'Ext.dd.DDTarget',
86405     requires: ['Ext.dd.ScrollManager'],
86406
86407     constructor : function(el, config){
86408         this.el = Ext.get(el);
86409
86410         Ext.apply(this, config);
86411
86412         if(this.containerScroll){
86413             Ext.dd.ScrollManager.register(this.el);
86414         }
86415
86416         this.callParent([this.el.dom, this.ddGroup || this.group,
86417               {isTarget: true}]);
86418     },
86419
86420     /**
86421      * @cfg {String} ddGroup
86422      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
86423      * interact with other drag drop objects in the same group (defaults to undefined).
86424      */
86425     /**
86426      * @cfg {String} overClass
86427      * The CSS class applied to the drop target element while the drag source is over it (defaults to "").
86428      */
86429     /**
86430      * @cfg {String} dropAllowed
86431      * The CSS class returned to the drag source when drop is allowed (defaults to "x-dd-drop-ok").
86432      */
86433     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
86434     /**
86435      * @cfg {String} dropNotAllowed
86436      * The CSS class returned to the drag source when drop is not allowed (defaults to "x-dd-drop-nodrop").
86437      */
86438     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
86439
86440     // private
86441     isTarget : true,
86442
86443     // private
86444     isNotifyTarget : true,
86445
86446     /**
86447      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source is now over the
86448      * target.  This default implementation adds the CSS class specified by overClass (if any) to the drop element
86449      * and returns the dropAllowed config value.  This method should be overridden if drop validation is required.
86450      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
86451      * @param {Event} e The event
86452      * @param {Object} data An object containing arbitrary data supplied by the drag source
86453      * @return {String} status The CSS class that communicates the drop status back to the source so that the
86454      * underlying {@link Ext.dd.StatusProxy} can be updated
86455      */
86456     notifyEnter : function(dd, e, data){
86457         if(this.overClass){
86458             this.el.addCls(this.overClass);
86459         }
86460         return this.dropAllowed;
86461     },
86462
86463     /**
86464      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the target.
86465      * This method will be called on every mouse movement while the drag source is over the drop target.
86466      * This default implementation simply returns the dropAllowed config value.
86467      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
86468      * @param {Event} e The event
86469      * @param {Object} data An object containing arbitrary data supplied by the drag source
86470      * @return {String} status The CSS class that communicates the drop status back to the source so that the
86471      * underlying {@link Ext.dd.StatusProxy} can be updated
86472      */
86473     notifyOver : function(dd, e, data){
86474         return this.dropAllowed;
86475     },
86476
86477     /**
86478      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source has been dragged
86479      * out of the target without dropping.  This default implementation simply removes the CSS class specified by
86480      * overClass (if any) from the drop element.
86481      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
86482      * @param {Event} e The event
86483      * @param {Object} data An object containing arbitrary data supplied by the drag source
86484      */
86485     notifyOut : function(dd, e, data){
86486         if(this.overClass){
86487             this.el.removeCls(this.overClass);
86488         }
86489     },
86490
86491     /**
86492      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the dragged item has
86493      * been dropped on it.  This method has no default implementation and returns false, so you must provide an
86494      * implementation that does something to process the drop event and returns true so that the drag source's
86495      * repair action does not run.
86496      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
86497      * @param {Event} e The event
86498      * @param {Object} data An object containing arbitrary data supplied by the drag source
86499      * @return {Boolean} False if the drop was invalid.
86500      */
86501     notifyDrop : function(dd, e, data){
86502         return false;
86503     },
86504
86505     destroy : function(){
86506         this.callParent();
86507         if(this.containerScroll){
86508             Ext.dd.ScrollManager.unregister(this.el);
86509         }
86510     }
86511 });
86512
86513 /**
86514  * @class Ext.dd.Registry
86515  * Provides easy access to all drag drop components that are registered on a page.  Items can be retrieved either
86516  * directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
86517  * @singleton
86518  */
86519 Ext.define('Ext.dd.Registry', {
86520     singleton: true,
86521     constructor: function() {
86522         this.elements = {}; 
86523         this.handles = {}; 
86524         this.autoIdSeed = 0;
86525     },
86526     
86527     getId: function(el, autogen){
86528         if(typeof el == "string"){
86529             return el;
86530         }
86531         var id = el.id;
86532         if(!id && autogen !== false){
86533             id = "extdd-" + (++this.autoIdSeed);
86534             el.id = id;
86535         }
86536         return id;
86537     },
86538     
86539     /**
86540      * Resgister a drag drop element
86541      * @param {String/HTMLElement} element The id or DOM node to register
86542      * @param {Object} data (optional) An custom data object that will be passed between the elements that are involved
86543      * in drag drop operations.  You can populate this object with any arbitrary properties that your own code
86544      * knows how to interpret, plus there are some specific properties known to the Registry that should be
86545      * populated in the data object (if applicable):
86546      * <pre>
86547 Value      Description<br />
86548 ---------  ------------------------------------------<br />
86549 handles    Array of DOM nodes that trigger dragging<br />
86550            for the element being registered<br />
86551 isHandle   True if the element passed in triggers<br />
86552            dragging itself, else false
86553 </pre>
86554      */
86555     register : function(el, data){
86556         data = data || {};
86557         if (typeof el == "string") {
86558             el = document.getElementById(el);
86559         }
86560         data.ddel = el;
86561         this.elements[this.getId(el)] = data;
86562         if (data.isHandle !== false) {
86563             this.handles[data.ddel.id] = data;
86564         }
86565         if (data.handles) {
86566             var hs = data.handles;
86567             for (var i = 0, len = hs.length; i < len; i++) {
86568                 this.handles[this.getId(hs[i])] = data;
86569             }
86570         }
86571     },
86572
86573     /**
86574      * Unregister a drag drop element
86575      * @param {String/HTMLElement} element The id or DOM node to unregister
86576      */
86577     unregister : function(el){
86578         var id = this.getId(el, false);
86579         var data = this.elements[id];
86580         if(data){
86581             delete this.elements[id];
86582             if(data.handles){
86583                 var hs = data.handles;
86584                 for (var i = 0, len = hs.length; i < len; i++) {
86585                     delete this.handles[this.getId(hs[i], false)];
86586                 }
86587             }
86588         }
86589     },
86590
86591     /**
86592      * Returns the handle registered for a DOM Node by id
86593      * @param {String/HTMLElement} id The DOM node or id to look up
86594      * @return {Object} handle The custom handle data
86595      */
86596     getHandle : function(id){
86597         if(typeof id != "string"){ // must be element?
86598             id = id.id;
86599         }
86600         return this.handles[id];
86601     },
86602
86603     /**
86604      * Returns the handle that is registered for the DOM node that is the target of the event
86605      * @param {Event} e The event
86606      * @return {Object} handle The custom handle data
86607      */
86608     getHandleFromEvent : function(e){
86609         var t = e.getTarget();
86610         return t ? this.handles[t.id] : null;
86611     },
86612
86613     /**
86614      * Returns a custom data object that is registered for a DOM node by id
86615      * @param {String/HTMLElement} id The DOM node or id to look up
86616      * @return {Object} data The custom data
86617      */
86618     getTarget : function(id){
86619         if(typeof id != "string"){ // must be element?
86620             id = id.id;
86621         }
86622         return this.elements[id];
86623     },
86624
86625     /**
86626      * Returns a custom data object that is registered for the DOM node that is the target of the event
86627      * @param {Event} e The event
86628      * @return {Object} data The custom data
86629      */
86630     getTargetFromEvent : function(e){
86631         var t = e.getTarget();
86632         return t ? this.elements[t.id] || this.handles[t.id] : null;
86633     }
86634 });
86635 /**
86636  * @class Ext.dd.DropZone
86637  * @extends Ext.dd.DropTarget
86638
86639 This class provides a container DD instance that allows dropping on multiple child target nodes.
86640
86641 By default, this class requires that child nodes accepting drop are registered with {@link Ext.dd.Registry}.
86642 However a simpler way to allow a DropZone to manage any number of target elements is to configure the
86643 DropZone with an implementation of {@link #getTargetFromEvent} which interrogates the passed
86644 mouse event to see if it has taken place within an element, or class of elements. This is easily done
86645 by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
86646 {@link Ext.DomQuery} selector.
86647
86648 Once the DropZone has detected through calling getTargetFromEvent, that the mouse is over
86649 a drop target, that target is passed as the first parameter to {@link #onNodeEnter}, {@link #onNodeOver},
86650 {@link #onNodeOut}, {@link #onNodeDrop}. You may configure the instance of DropZone with implementations
86651 of these methods to provide application-specific behaviour for these events to update both
86652 application state, and UI state.
86653
86654 For example to make a GridPanel a cooperating target with the example illustrated in
86655 {@link Ext.dd.DragZone DragZone}, the following technique might be used:
86656
86657     myGridPanel.on('render', function() {
86658         myGridPanel.dropZone = new Ext.dd.DropZone(myGridPanel.getView().scroller, {
86659
86660             // If the mouse is over a grid row, return that node. This is
86661             // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
86662             getTargetFromEvent: function(e) {
86663                 return e.getTarget(myGridPanel.getView().rowSelector);
86664             },
86665
86666             // On entry into a target node, highlight that node.
86667             onNodeEnter : function(target, dd, e, data){ 
86668                 Ext.fly(target).addCls('my-row-highlight-class');
86669             },
86670
86671             // On exit from a target node, unhighlight that node.
86672             onNodeOut : function(target, dd, e, data){ 
86673                 Ext.fly(target).removeCls('my-row-highlight-class');
86674             },
86675
86676             // While over a target node, return the default drop allowed class which
86677             // places a "tick" icon into the drag proxy.
86678             onNodeOver : function(target, dd, e, data){ 
86679                 return Ext.dd.DropZone.prototype.dropAllowed;
86680             },
86681
86682             // On node drop we can interrogate the target to find the underlying
86683             // application object that is the real target of the dragged data.
86684             // In this case, it is a Record in the GridPanel's Store.
86685             // We can use the data set up by the DragZone's getDragData method to read
86686             // any data we decided to attach in the DragZone's getDragData method.
86687             onNodeDrop : function(target, dd, e, data){
86688                 var rowIndex = myGridPanel.getView().findRowIndex(target);
86689                 var r = myGridPanel.getStore().getAt(rowIndex);
86690                 Ext.Msg.alert('Drop gesture', 'Dropped Record id ' + data.draggedRecord.id +
86691                     ' on Record id ' + r.id);
86692                 return true;
86693             }
86694         });
86695     }
86696
86697 See the {@link Ext.dd.DragZone DragZone} documentation for details about building a DragZone which
86698 cooperates with this DropZone.
86699
86700  * @constructor
86701  * @param {Mixed} el The container element
86702  * @param {Object} config
86703  * @markdown
86704  */
86705 Ext.define('Ext.dd.DropZone', {
86706     extend: 'Ext.dd.DropTarget',
86707     requires: ['Ext.dd.Registry'],
86708
86709     /**
86710      * Returns a custom data object associated with the DOM node that is the target of the event.  By default
86711      * this looks up the event target in the {@link Ext.dd.Registry}, although you can override this method to
86712      * provide your own custom lookup.
86713      * @param {Event} e The event
86714      * @return {Object} data The custom data
86715      */
86716     getTargetFromEvent : function(e){
86717         return Ext.dd.Registry.getTargetFromEvent(e);
86718     },
86719
86720     /**
86721      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
86722      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
86723      * This method has no default implementation and should be overridden to provide
86724      * node-specific processing if necessary.
86725      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from 
86726      * {@link #getTargetFromEvent} for this node)
86727      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86728      * @param {Event} e The event
86729      * @param {Object} data An object containing arbitrary data supplied by the drag source
86730      */
86731     onNodeEnter : function(n, dd, e, data){
86732         
86733     },
86734
86735     /**
86736      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
86737      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
86738      * The default implementation returns this.dropNotAllowed, so it should be
86739      * overridden to provide the proper feedback.
86740      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
86741      * {@link #getTargetFromEvent} for this node)
86742      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86743      * @param {Event} e The event
86744      * @param {Object} data An object containing arbitrary data supplied by the drag source
86745      * @return {String} status The CSS class that communicates the drop status back to the source so that the
86746      * underlying {@link Ext.dd.StatusProxy} can be updated
86747      */
86748     onNodeOver : function(n, dd, e, data){
86749         return this.dropAllowed;
86750     },
86751
86752     /**
86753      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged out of
86754      * the drop node without dropping.  This method has no default implementation and should be overridden to provide
86755      * node-specific processing if necessary.
86756      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
86757      * {@link #getTargetFromEvent} for this node)
86758      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86759      * @param {Event} e The event
86760      * @param {Object} data An object containing arbitrary data supplied by the drag source
86761      */
86762     onNodeOut : function(n, dd, e, data){
86763         
86764     },
86765
86766     /**
86767      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
86768      * the drop node.  The default implementation returns false, so it should be overridden to provide the
86769      * appropriate processing of the drop event and return true so that the drag source's repair action does not run.
86770      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
86771      * {@link #getTargetFromEvent} for this node)
86772      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86773      * @param {Event} e The event
86774      * @param {Object} data An object containing arbitrary data supplied by the drag source
86775      * @return {Boolean} True if the drop was valid, else false
86776      */
86777     onNodeDrop : function(n, dd, e, data){
86778         return false;
86779     },
86780
86781     /**
86782      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is being dragged over it,
86783      * but not over any of its registered drop nodes.  The default implementation returns this.dropNotAllowed, so
86784      * it should be overridden to provide the proper feedback if necessary.
86785      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86786      * @param {Event} e The event
86787      * @param {Object} data An object containing arbitrary data supplied by the drag source
86788      * @return {String} status The CSS class that communicates the drop status back to the source so that the
86789      * underlying {@link Ext.dd.StatusProxy} can be updated
86790      */
86791     onContainerOver : function(dd, e, data){
86792         return this.dropNotAllowed;
86793     },
86794
86795     /**
86796      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped on it,
86797      * but not on any of its registered drop nodes.  The default implementation returns false, so it should be
86798      * overridden to provide the appropriate processing of the drop event if you need the drop zone itself to
86799      * be able to accept drops.  It should return true when valid so that the drag source's repair action does not run.
86800      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86801      * @param {Event} e The event
86802      * @param {Object} data An object containing arbitrary data supplied by the drag source
86803      * @return {Boolean} True if the drop was valid, else false
86804      */
86805     onContainerDrop : function(dd, e, data){
86806         return false;
86807     },
86808
86809     /**
86810      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source is now over
86811      * the zone.  The default implementation returns this.dropNotAllowed and expects that only registered drop
86812      * nodes can process drag drop operations, so if you need the drop zone itself to be able to process drops
86813      * you should override this method and provide a custom implementation.
86814      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86815      * @param {Event} e The event
86816      * @param {Object} data An object containing arbitrary data supplied by the drag source
86817      * @return {String} status The CSS class that communicates the drop status back to the source so that the
86818      * underlying {@link Ext.dd.StatusProxy} can be updated
86819      */
86820     notifyEnter : function(dd, e, data){
86821         return this.dropNotAllowed;
86822     },
86823
86824     /**
86825      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the drop zone.
86826      * This method will be called on every mouse movement while the drag source is over the drop zone.
86827      * It will call {@link #onNodeOver} while the drag source is over a registered node, and will also automatically
86828      * delegate to the appropriate node-specific methods as necessary when the drag source enters and exits
86829      * registered nodes ({@link #onNodeEnter}, {@link #onNodeOut}). If the drag source is not currently over a
86830      * registered node, it will call {@link #onContainerOver}.
86831      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86832      * @param {Event} e The event
86833      * @param {Object} data An object containing arbitrary data supplied by the drag source
86834      * @return {String} status The CSS class that communicates the drop status back to the source so that the
86835      * underlying {@link Ext.dd.StatusProxy} can be updated
86836      */
86837     notifyOver : function(dd, e, data){
86838         var n = this.getTargetFromEvent(e);
86839         if(!n) { // not over valid drop target
86840             if(this.lastOverNode){
86841                 this.onNodeOut(this.lastOverNode, dd, e, data);
86842                 this.lastOverNode = null;
86843             }
86844             return this.onContainerOver(dd, e, data);
86845         }
86846         if(this.lastOverNode != n){
86847             if(this.lastOverNode){
86848                 this.onNodeOut(this.lastOverNode, dd, e, data);
86849             }
86850             this.onNodeEnter(n, dd, e, data);
86851             this.lastOverNode = n;
86852         }
86853         return this.onNodeOver(n, dd, e, data);
86854     },
86855
86856     /**
86857      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source has been dragged
86858      * out of the zone without dropping.  If the drag source is currently over a registered node, the notification
86859      * will be delegated to {@link #onNodeOut} for node-specific handling, otherwise it will be ignored.
86860      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
86861      * @param {Event} e The event
86862      * @param {Object} data An object containing arbitrary data supplied by the drag zone
86863      */
86864     notifyOut : function(dd, e, data){
86865         if(this.lastOverNode){
86866             this.onNodeOut(this.lastOverNode, dd, e, data);
86867             this.lastOverNode = null;
86868         }
86869     },
86870
86871     /**
86872      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the dragged item has
86873      * been dropped on it.  The drag zone will look up the target node based on the event passed in, and if there
86874      * is a node registered for that event, it will delegate to {@link #onNodeDrop} for node-specific handling,
86875      * otherwise it will call {@link #onContainerDrop}.
86876      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
86877      * @param {Event} e The event
86878      * @param {Object} data An object containing arbitrary data supplied by the drag source
86879      * @return {Boolean} False if the drop was invalid.
86880      */
86881     notifyDrop : function(dd, e, data){
86882         if(this.lastOverNode){
86883             this.onNodeOut(this.lastOverNode, dd, e, data);
86884             this.lastOverNode = null;
86885         }
86886         var n = this.getTargetFromEvent(e);
86887         return n ?
86888             this.onNodeDrop(n, dd, e, data) :
86889             this.onContainerDrop(dd, e, data);
86890     },
86891
86892     // private
86893     triggerCacheRefresh : function() {
86894         Ext.dd.DDM.refreshCache(this.groups);
86895     }
86896 });
86897 /**
86898  * @class Ext.flash.Component
86899  * @extends Ext.Component
86900  *
86901  * A simple Component for displaying an Adobe Flash SWF movie. The movie will be sized and can participate
86902  * in layout like any other Component.
86903  *
86904  * This component requires the third-party SWFObject library version 2.2 or above. It is not included within
86905  * the ExtJS distribution, so you will have to include it into your page manually in order to use this component.
86906  * The SWFObject library can be downloaded from the [SWFObject project page](http://code.google.com/p/swfobject)
86907  * and then simply import it into the head of your HTML document:
86908  *
86909  *     <script type="text/javascript" src="path/to/local/swfobject.js"></script>
86910  *
86911  * ## Configuration
86912  *
86913  * This component allows several options for configuring how the target Flash movie is embedded. The most
86914  * important is the required {@link #url} which points to the location of the Flash movie to load. Other
86915  * configurations include:
86916  *
86917  * - {@link #backgroundColor}
86918  * - {@link #wmode}
86919  * - {@link #flashVars}
86920  * - {@link #flashParams}
86921  * - {@link #flashAttributes}
86922  *
86923  * ## Example usage:
86924  *
86925  *     var win = Ext.widget('window', {
86926  *         title: "It's a tiger!",
86927  *         layout: 'fit',
86928  *         width: 300,
86929  *         height: 300,
86930  *         x: 20,
86931  *         y: 20,
86932  *         resizable: true,
86933  *         items: {
86934  *             xtype: 'flash',
86935  *             url: 'tiger.swf'
86936  *         }
86937  *     });
86938  *     win.show();
86939  *
86940  * ## Express Install
86941  *
86942  * Adobe provides a tool called [Express Install](http://www.adobe.com/devnet/flashplayer/articles/express_install.html)
86943  * that offers users an easy way to upgrade their Flash player. If you wish to make use of this, you should set
86944  * the static EXPRESS\_INSTALL\_URL property to the location of your Express Install SWF file:
86945  *
86946  *     Ext.flash.Component.EXPRESS_INSTALL_URL = 'path/to/local/expressInstall.swf';
86947  *
86948  * @constructor
86949  * Creates a new Ext.flash.Component instance.
86950  * @param {Object} config The component configuration.
86951  *
86952  * @xtype flash
86953  * @docauthor Jason Johnston <jason@sencha.com>
86954  */
86955 Ext.define('Ext.flash.Component', {
86956     extend: 'Ext.Component',
86957     alternateClassName: 'Ext.FlashComponent',
86958     alias: 'widget.flash',
86959
86960     /**
86961      * @cfg {String} flashVersion
86962      * Indicates the version the flash content was published for. Defaults to <tt>'9.0.115'</tt>.
86963      */
86964     flashVersion : '9.0.115',
86965
86966     /**
86967      * @cfg {String} backgroundColor
86968      * The background color of the SWF movie. Defaults to <tt>'#ffffff'</tt>.
86969      */
86970     backgroundColor: '#ffffff',
86971
86972     /**
86973      * @cfg {String} wmode
86974      * The wmode of the flash object. This can be used to control layering. Defaults to <tt>'opaque'</tt>.
86975      * Set to 'transparent' to ignore the {@link #backgroundColor} and make the background of the Flash
86976      * movie transparent.
86977      */
86978     wmode: 'opaque',
86979
86980     /**
86981      * @cfg {Object} flashVars
86982      * A set of key value pairs to be passed to the flash object as flash variables. Defaults to <tt>undefined</tt>.
86983      */
86984
86985     /**
86986      * @cfg {Object} flashParams
86987      * A set of key value pairs to be passed to the flash object as parameters. Possible parameters can be found here:
86988      * http://kb2.adobe.com/cps/127/tn_12701.html Defaults to <tt>undefined</tt>.
86989      */
86990
86991     /**
86992      * @cfg {Object} flashAttributes
86993      * A set of key value pairs to be passed to the flash object as attributes. Defaults to <tt>undefined</tt>.
86994      */
86995
86996     /**
86997      * @cfg {String} url
86998      * The URL of the SWF file to include. Required.
86999      */
87000
87001     /**
87002      * @cfg {String/Number} swfWidth The width of the embedded SWF movie inside the component. Defaults to "100%"
87003      * so that the movie matches the width of the component.
87004      */
87005     swfWidth: '100%',
87006
87007     /**
87008      * @cfg {String/Number} swfHeight The height of the embedded SWF movie inside the component. Defaults to "100%"
87009      * so that the movie matches the height of the component.
87010      */
87011     swfHeight: '100%',
87012
87013     /**
87014      * @cfg {Boolean} expressInstall
87015      * True to prompt the user to install flash if not installed. Note that this uses
87016      * Ext.FlashComponent.EXPRESS_INSTALL_URL, which should be set to the local resource. Defaults to <tt>false</tt>.
87017      */
87018     expressInstall: false,
87019
87020     /**
87021      * @property swf
87022      * @type {Ext.core.Element}
87023      * A reference to the object or embed element into which the SWF file is loaded. Only
87024      * populated after the component is rendered and the SWF has been successfully embedded.
87025      */
87026
87027     // Have to create a placeholder div with the swfId, which SWFObject will replace with the object/embed element.
87028     renderTpl: ['<div id="{swfId}"></div>'],
87029
87030     initComponent: function() {
87031         if (!('swfobject' in window)) {
87032             Ext.Error.raise('The SWFObject library is not loaded. Ext.flash.Component requires SWFObject version 2.2 or later: http://code.google.com/p/swfobject/');
87033         }
87034         if (!this.url) {
87035             Ext.Error.raise('The "url" config is required for Ext.flash.Component');
87036         }
87037
87038         this.callParent();
87039         this.addEvents(
87040             /**
87041              * @event success
87042              * Fired when the Flash movie has been successfully embedded
87043              * @param {Ext.flash.Component} this
87044              */
87045             'success',
87046
87047             /**
87048              * @event failure
87049              * Fired when the Flash movie embedding fails
87050              * @param {Ext.flash.Component} this
87051              */
87052             'failure'
87053         );
87054     },
87055
87056     onRender: function() {
87057         var me = this,
87058             params, vars, undef,
87059             swfId = me.getSwfId();
87060
87061         me.renderData.swfId = swfId;
87062
87063         me.callParent(arguments);
87064
87065         params = Ext.apply({
87066             allowScriptAccess: 'always',
87067             bgcolor: me.backgroundColor,
87068             wmode: me.wmode
87069         }, me.flashParams);
87070
87071         vars = Ext.apply({
87072             allowedDomain: document.location.hostname
87073         }, me.flashVars);
87074
87075         new swfobject.embedSWF(
87076             me.url,
87077             swfId,
87078             me.swfWidth,
87079             me.swfHeight,
87080             me.flashVersion,
87081             me.expressInstall ? me.statics.EXPRESS_INSTALL_URL : undef,
87082             vars,
87083             params,
87084             me.flashAttributes,
87085             Ext.bind(me.swfCallback, me)
87086         );
87087     },
87088
87089     /**
87090      * @private
87091      * The callback method for handling an embedding success or failure by SWFObject
87092      * @param {Object} e The event object passed by SWFObject - see http://code.google.com/p/swfobject/wiki/api
87093      */
87094     swfCallback: function(e) {
87095         var me = this;
87096         if (e.success) {
87097             me.swf = Ext.get(e.ref);
87098             me.onSuccess();
87099             me.fireEvent('success', me);
87100         } else {
87101             me.onFailure();
87102             me.fireEvent('failure', me);
87103         }
87104     },
87105
87106     /**
87107      * Retrieve the id of the SWF object/embed element
87108      */
87109     getSwfId: function() {
87110         return this.swfId || (this.swfId = "extswf" + this.getAutoId());
87111     },
87112
87113     onSuccess: function() {
87114         // swfobject forces visiblity:visible on the swf element, which prevents it 
87115         // from getting hidden when an ancestor is given visibility:hidden.
87116         this.swf.setStyle('visibility', 'inherit');
87117     },
87118
87119     onFailure: Ext.emptyFn,
87120
87121     beforeDestroy: function() {
87122         var me = this,
87123             swf = me.swf;
87124         if (swf) {
87125             swfobject.removeSWF(me.getSwfId());
87126             Ext.destroy(swf);
87127             delete me.swf;
87128         }
87129         me.callParent();
87130     },
87131
87132     statics: {
87133         /**
87134          * Sets the url for installing flash if it doesn't exist. This should be set to a local resource.
87135          * See http://www.adobe.com/devnet/flashplayer/articles/express_install.html for details.
87136          * @static
87137          * @type String
87138          */
87139         EXPRESS_INSTALL_URL: 'http:/' + '/swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf'
87140     }
87141 });
87142
87143 /**
87144  * @class Ext.form.action.Action
87145  * @extends Ext.Base
87146  * <p>The subclasses of this class provide actions to perform upon {@link Ext.form.Basic Form}s.</p>
87147  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
87148  * the Form needs to perform an action such as submit or load. The Configuration options
87149  * listed for this class are set through the Form's action methods: {@link Ext.form.Basic#submit submit},
87150  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}</p>
87151  * <p>The instance of Action which performed the action is passed to the success
87152  * and failure callbacks of the Form's action methods ({@link Ext.form.Basic#submit submit},
87153  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}),
87154  * and to the {@link Ext.form.Basic#actioncomplete actioncomplete} and
87155  * {@link Ext.form.Basic#actionfailed actionfailed} event handlers.</p>
87156  * @constructor
87157  * @param {Object} config The configuration for this instance.
87158  */
87159 Ext.define('Ext.form.action.Action', {
87160     alternateClassName: 'Ext.form.Action',
87161
87162     /**
87163      * @cfg {Ext.form.Basic} form The {@link Ext.form.Basic BasicForm} instance that
87164      * is invoking this Action. Required.
87165      */
87166
87167     /**
87168      * @cfg {String} url The URL that the Action is to invoke. Will default to the {@link Ext.form.Basic#url url}
87169      * configured on the {@link #form}.
87170      */
87171
87172     /**
87173      * @cfg {Boolean} reset When set to <tt><b>true</b></tt>, causes the Form to be
87174      * {@link Ext.form.Basic#reset reset} on Action success. If specified, this happens
87175      * before the {@link #success} callback is called and before the Form's
87176      * {@link Ext.form.Basic#actioncomplete actioncomplete} event fires.
87177      */
87178
87179     /**
87180      * @cfg {String} method The HTTP method to use to access the requested URL. Defaults to the
87181      * {@link Ext.form.Basic#method BasicForm's method}, or 'POST' if not specified.
87182      */
87183
87184     /**
87185      * @cfg {Object/String} params <p>Extra parameter values to pass. These are added to the Form's
87186      * {@link Ext.form.Basic#baseParams} and passed to the specified URL along with the Form's
87187      * input fields.</p>
87188      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p>
87189      */
87190
87191     /**
87192      * @cfg {Object} headers <p>Extra headers to be sent in the AJAX request for submit and load actions. See
87193      * {@link Ext.data.Connection#headers}.</p>
87194      */
87195
87196     /**
87197      * @cfg {Number} timeout The number of seconds to wait for a server response before
87198      * failing with the {@link #failureType} as {@link Ext.form.action.Action#CONNECT_FAILURE}. If not specified,
87199      * defaults to the configured <tt>{@link Ext.form.Basic#timeout timeout}</tt> of the
87200      * {@link #form}.
87201      */
87202
87203     /**
87204      * @cfg {Function} success The function to call when a valid success return packet is received.
87205      * The function is passed the following parameters:<ul class="mdetail-params">
87206      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
87207      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. The {@link #result}
87208      * property of this object may be examined to perform custom postprocessing.</div></li>
87209      * </ul>
87210      */
87211
87212     /**
87213      * @cfg {Function} failure The function to call when a failure packet was received, or when an
87214      * error ocurred in the Ajax communication.
87215      * The function is passed the following parameters:<ul class="mdetail-params">
87216      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
87217      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. If an Ajax
87218      * error ocurred, the failure type will be in {@link #failureType}. The {@link #result}
87219      * property of this object may be examined to perform custom postprocessing.</div></li>
87220      * </ul>
87221      */
87222
87223     /**
87224      * @cfg {Object} scope The scope in which to call the configured <tt>success</tt> and <tt>failure</tt>
87225      * callback functions (the <tt>this</tt> reference for the callback functions).
87226      */
87227
87228     /**
87229      * @cfg {String} waitMsg The message to be displayed by a call to {@link Ext.window.MessageBox#wait}
87230      * during the time the action is being processed.
87231      */
87232
87233     /**
87234      * @cfg {String} waitTitle The title to be displayed by a call to {@link Ext.window.MessageBox#wait}
87235      * during the time the action is being processed.
87236      */
87237
87238     /**
87239      * @cfg {Boolean} submitEmptyText If set to <tt>true</tt>, the emptyText value will be sent with the form
87240      * when it is submitted. Defaults to <tt>true</tt>.
87241      */
87242
87243     /**
87244      * @property type
87245      * The type of action this Action instance performs.
87246      * Currently only "submit" and "load" are supported.
87247      * @type {String}
87248      */
87249
87250     /**
87251      * The type of failure detected will be one of these: {@link Ext.form.action.Action#CLIENT_INVALID},
87252      * {@link Ext.form.action.Action#SERVER_INVALID}, {@link Ext.form.action.Action#CONNECT_FAILURE}, or
87253      * {@link Ext.form.action.Action#LOAD_FAILURE}.  Usage:
87254      * <pre><code>
87255 var fp = new Ext.form.Panel({
87256 ...
87257 buttons: [{
87258     text: 'Save',
87259     formBind: true,
87260     handler: function(){
87261         if(fp.getForm().isValid()){
87262             fp.getForm().submit({
87263                 url: 'form-submit.php',
87264                 waitMsg: 'Submitting your data...',
87265                 success: function(form, action){
87266                     // server responded with success = true
87267                     var result = action.{@link #result};
87268                 },
87269                 failure: function(form, action){
87270                     if (action.{@link #failureType} === {@link Ext.form.action.Action#CONNECT_FAILURE}) {
87271                         Ext.Msg.alert('Error',
87272                             'Status:'+action.{@link #response}.status+': '+
87273                             action.{@link #response}.statusText);
87274                     }
87275                     if (action.failureType === {@link Ext.form.action.Action#SERVER_INVALID}){
87276                         // server responded with success = false
87277                         Ext.Msg.alert('Invalid', action.{@link #result}.errormsg);
87278                     }
87279                 }
87280             });
87281         }
87282     }
87283 },{
87284     text: 'Reset',
87285     handler: function(){
87286         fp.getForm().reset();
87287     }
87288 }]
87289      * </code></pre>
87290      * @property failureType
87291      * @type {String}
87292      */
87293
87294     /**
87295      * The raw XMLHttpRequest object used to perform the action.
87296      * @property response
87297      * @type {Object}
87298      */
87299
87300     /**
87301      * The decoded response object containing a boolean <tt>success</tt> property and
87302      * other, action-specific properties.
87303      * @property result
87304      * @type {Object}
87305      */
87306
87307
87308
87309     constructor: function(config) {
87310         if (config) {
87311             Ext.apply(this, config);
87312         }
87313
87314         // Normalize the params option to an Object
87315         var params = config.params;
87316         if (Ext.isString(params)) {
87317             this.params = Ext.Object.fromQueryString(params);
87318         }
87319     },
87320
87321     /**
87322      * Invokes this action using the current configuration.
87323      */
87324     run: Ext.emptyFn,
87325
87326     /**
87327      * @private
87328      * @method onSuccess
87329      * Callback method that gets invoked when the action completes successfully. Must be implemented by subclasses.
87330      * @param {Object} response
87331      */
87332
87333     /**
87334      * @private
87335      * @method handleResponse
87336      * Handles the raw response and builds a result object from it. Must be implemented by subclasses.
87337      * @param {Object} response
87338      */
87339
87340     /**
87341      * @private
87342      * Handles a failure response.
87343      * @param {Object} response
87344      */
87345     onFailure : function(response){
87346         this.response = response;
87347         this.failureType = Ext.form.action.Action.CONNECT_FAILURE;
87348         this.form.afterAction(this, false);
87349     },
87350
87351     /**
87352      * @private
87353      * Validates that a response contains either responseText or responseXML and invokes
87354      * {@link #handleResponse} to build the result object.
87355      * @param {Object} response The raw response object.
87356      * @return {Object/Boolean} result The result object as built by handleResponse, or <tt>true</tt> if
87357      *                         the response had empty responseText and responseXML.
87358      */
87359     processResponse : function(response){
87360         this.response = response;
87361         if (!response.responseText && !response.responseXML) {
87362             return true;
87363         }
87364         return (this.result = this.handleResponse(response));
87365     },
87366
87367     /**
87368      * @private
87369      * Build the URL for the AJAX request. Used by the standard AJAX submit and load actions.
87370      * @return {String} The URL.
87371      */
87372     getUrl: function() {
87373         return this.url || this.form.url;
87374     },
87375
87376     /**
87377      * @private
87378      * Determine the HTTP method to be used for the request.
87379      * @return {String} The HTTP method
87380      */
87381     getMethod: function() {
87382         return (this.method || this.form.method || 'POST').toUpperCase();
87383     },
87384
87385     /**
87386      * @private
87387      * Get the set of parameters specified in the BasicForm's baseParams and/or the params option.
87388      * Items in params override items of the same name in baseParams.
87389      * @return {Object} the full set of parameters
87390      */
87391     getParams: function() {
87392         return Ext.apply({}, this.params, this.form.baseParams);
87393     },
87394
87395     /**
87396      * @private
87397      * Creates a callback object.
87398      */
87399     createCallback: function() {
87400         var me = this,
87401             undef,
87402             form = me.form;
87403         return {
87404             success: me.onSuccess,
87405             failure: me.onFailure,
87406             scope: me,
87407             timeout: (this.timeout * 1000) || (form.timeout * 1000),
87408             upload: form.fileUpload ? me.onSuccess : undef
87409         };
87410     },
87411
87412     statics: {
87413         /**
87414          * @property CLIENT_INVALID
87415          * Failure type returned when client side validation of the Form fails
87416          * thus aborting a submit action. Client side validation is performed unless
87417          * {@link Ext.form.action.Submit#clientValidation} is explicitly set to <tt>false</tt>.
87418          * @type {String}
87419          * @static
87420          */
87421         CLIENT_INVALID: 'client',
87422
87423         /**
87424          * @property SERVER_INVALID
87425          * <p>Failure type returned when server side processing fails and the {@link #result}'s
87426          * <tt>success</tt> property is set to <tt>false</tt>.</p>
87427          * <p>In the case of a form submission, field-specific error messages may be returned in the
87428          * {@link #result}'s <tt>errors</tt> property.</p>
87429          * @type {String}
87430          * @static
87431          */
87432         SERVER_INVALID: 'server',
87433
87434         /**
87435          * @property CONNECT_FAILURE
87436          * Failure type returned when a communication error happens when attempting
87437          * to send a request to the remote server. The {@link #response} may be examined to
87438          * provide further information.
87439          * @type {String}
87440          * @static
87441          */
87442         CONNECT_FAILURE: 'connect',
87443
87444         /**
87445          * @property LOAD_FAILURE
87446          * Failure type returned when the response's <tt>success</tt>
87447          * property is set to <tt>false</tt>, or no field values are returned in the response's
87448          * <tt>data</tt> property.
87449          * @type {String}
87450          * @static
87451          */
87452         LOAD_FAILURE: 'load'
87453
87454
87455     }
87456 });
87457
87458 /**
87459  * @class Ext.form.action.Submit
87460  * @extends Ext.form.action.Action
87461  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s
87462  * and processes the returned response.</p>
87463  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
87464  * {@link Ext.form.Basic#submit submit}ting.</p>
87465  * <p><u><b>Response Packet Criteria</b></u></p>
87466  * <p>A response packet may contain:
87467  * <div class="mdetail-params"><ul>
87468  * <li><b><code>success</code></b> property : Boolean
87469  * <div class="sub-desc">The <code>success</code> property is required.</div></li>
87470  * <li><b><code>errors</code></b> property : Object
87471  * <div class="sub-desc"><div class="sub-desc">The <code>errors</code> property,
87472  * which is optional, contains error messages for invalid fields.</div></li>
87473  * </ul></div>
87474  * <p><u><b>JSON Packets</b></u></p>
87475  * <p>By default, response packets are assumed to be JSON, so a typical response
87476  * packet may look like this:</p><pre><code>
87477 {
87478     success: false,
87479     errors: {
87480         clientCode: "Client not found",
87481         portOfLoading: "This field must not be null"
87482     }
87483 }</code></pre>
87484  * <p>Other data may be placed into the response for processing by the {@link Ext.form.Basic}'s callback
87485  * or event handler methods. The object decoded from this JSON is available in the
87486  * {@link Ext.form.action.Action#result result} property.</p>
87487  * <p>Alternatively, if an {@link #errorReader} is specified as an {@link Ext.data.reader.Xml XmlReader}:</p><pre><code>
87488     errorReader: new Ext.data.reader.Xml({
87489             record : 'field',
87490             success: '@success'
87491         }, [
87492             'id', 'msg'
87493         ]
87494     )
87495 </code></pre>
87496  * <p>then the results may be sent back in XML format:</p><pre><code>
87497 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
87498 &lt;message success="false"&gt;
87499 &lt;errors&gt;
87500     &lt;field&gt;
87501         &lt;id&gt;clientCode&lt;/id&gt;
87502         &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;
87503     &lt;/field&gt;
87504     &lt;field&gt;
87505         &lt;id&gt;portOfLoading&lt;/id&gt;
87506         &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;
87507     &lt;/field&gt;
87508 &lt;/errors&gt;
87509 &lt;/message&gt;
87510 </code></pre>
87511  * <p>Other elements may be placed into the response XML for processing by the {@link Ext.form.Basic}'s callback
87512  * or event handler methods. The XML document is available in the {@link #errorReader}'s {@link Ext.data.reader.Xml#xmlData xmlData} property.</p>
87513  */
87514 Ext.define('Ext.form.action.Submit', {
87515     extend:'Ext.form.action.Action',
87516     alternateClassName: 'Ext.form.Action.Submit',
87517     alias: 'formaction.submit',
87518
87519     type: 'submit',
87520
87521     /**
87522      * @cfg {boolean} clientValidation Determines whether a Form's fields are validated
87523      * in a final call to {@link Ext.form.Basic#isValid isValid} prior to submission.
87524      * Pass <tt>false</tt> in the Form's submit options to prevent this. Defaults to true.
87525      */
87526
87527     // inherit docs
87528     run : function(){
87529         var form = this.form;
87530         if (this.clientValidation === false || form.isValid()) {
87531             this.doSubmit();
87532         } else {
87533             // client validation failed
87534             this.failureType = Ext.form.action.Action.CLIENT_INVALID;
87535             form.afterAction(this, false);
87536         }
87537     },
87538
87539     /**
87540      * @private
87541      * Perform the submit of the form data.
87542      */
87543     doSubmit: function() {
87544         var formEl,
87545             ajaxOptions = Ext.apply(this.createCallback(), {
87546                 url: this.getUrl(),
87547                 method: this.getMethod(),
87548                 headers: this.headers
87549             });
87550
87551         // For uploads we need to create an actual form that contains the file upload fields,
87552         // and pass that to the ajax call so it can do its iframe-based submit method.
87553         if (this.form.hasUpload()) {
87554             formEl = ajaxOptions.form = this.buildForm();
87555             ajaxOptions.isUpload = true;
87556         } else {
87557             ajaxOptions.params = this.getParams();
87558         }
87559
87560         Ext.Ajax.request(ajaxOptions);
87561
87562         if (formEl) {
87563             Ext.removeNode(formEl);
87564         }
87565     },
87566
87567     /**
87568      * @private
87569      * Build the full set of parameters from the field values plus any additional configured params.
87570      */
87571     getParams: function() {
87572         var nope = false,
87573             configParams = this.callParent(),
87574             fieldParams = this.form.getValues(nope, nope, this.submitEmptyText !== nope);
87575         return Ext.apply({}, fieldParams, configParams);
87576     },
87577
87578     /**
87579      * @private
87580      * Build a form element containing fields corresponding to all the parameters to be
87581      * submitted (everything returned by {@link #getParams}.
87582      * NOTE: the form element is automatically added to the DOM, so any code that uses
87583      * it must remove it from the DOM after finishing with it.
87584      * @return HTMLFormElement
87585      */
87586     buildForm: function() {
87587         var fieldsSpec = [],
87588             formSpec,
87589             formEl,
87590             basicForm = this.form,
87591             params = this.getParams(),
87592             uploadFields = [];
87593
87594         basicForm.getFields().each(function(field) {
87595             if (field.isFileUpload()) {
87596                 uploadFields.push(field);
87597             }
87598         });
87599
87600         function addField(name, val) {
87601             fieldsSpec.push({
87602                 tag: 'input',
87603                 type: 'hidden',
87604                 name: name,
87605                 value: Ext.String.htmlEncode(val)
87606             });
87607         }
87608
87609         // Add the form field values
87610         Ext.iterate(params, function(key, val) {
87611             if (Ext.isArray(val)) {
87612                 Ext.each(val, function(v) {
87613                     addField(key, v);
87614                 });
87615             } else {
87616                 addField(key, val);
87617             }
87618         });
87619
87620         formSpec = {
87621             tag: 'form',
87622             action: this.getUrl(),
87623             method: this.getMethod(),
87624             target: this.target || '_self',
87625             style: 'display:none',
87626             cn: fieldsSpec
87627         };
87628
87629         // Set the proper encoding for file uploads
87630         if (uploadFields.length) {
87631             formSpec.encoding = formSpec.enctype = 'multipart/form-data';
87632         }
87633
87634         // Create the form
87635         formEl = Ext.core.DomHelper.append(Ext.getBody(), formSpec);
87636
87637         // Special handling for file upload fields: since browser security measures prevent setting
87638         // their values programatically, and prevent carrying their selected values over when cloning,
87639         // we have to move the actual field instances out of their components and into the form.
87640         Ext.Array.each(uploadFields, function(field) {
87641             if (field.rendered) { // can only have a selected file value after being rendered
87642                 formEl.appendChild(field.extractFileInput());
87643             }
87644         });
87645
87646         return formEl;
87647     },
87648
87649
87650
87651     /**
87652      * @private
87653      */
87654     onSuccess: function(response) {
87655         var form = this.form,
87656             success = true,
87657             result = this.processResponse(response);
87658         if (result !== true && !result.success) {
87659             if (result.errors) {
87660                 form.markInvalid(result.errors);
87661             }
87662             this.failureType = Ext.form.action.Action.SERVER_INVALID;
87663             success = false;
87664         }
87665         form.afterAction(this, success);
87666     },
87667
87668     /**
87669      * @private
87670      */
87671     handleResponse: function(response) {
87672         var form = this.form,
87673             errorReader = form.errorReader,
87674             rs, errors, i, len, records;
87675         if (errorReader) {
87676             rs = errorReader.read(response);
87677             records = rs.records;
87678             errors = [];
87679             if (records) {
87680                 for(i = 0, len = records.length; i < len; i++) {
87681                     errors[i] = records[i].data;
87682                 }
87683             }
87684             if (errors.length < 1) {
87685                 errors = null;
87686             }
87687             return {
87688                 success : rs.success,
87689                 errors : errors
87690             };
87691         }
87692         return Ext.decode(response.responseText);
87693     }
87694 });
87695
87696 /**
87697  * @class Ext.util.ComponentDragger
87698  * @extends Ext.dd.DragTracker
87699  * <p>A subclass of Ext.dd.DragTracker which handles dragging any Component.</p>
87700  * <p>This is configured with a Component to be made draggable, and a config object for the
87701  * {@link Ext.dd.DragTracker} class.</p>
87702  * <p>A {@link #} delegate may be provided which may be either the element to use as the mousedown target
87703  * or a {@link Ext.DomQuery} selector to activate multiple mousedown targets.</p>
87704  * @constructor Create a new ComponentTracker
87705  * @param {object} comp The Component to provide dragging for.
87706  * @param {object} config The config object
87707  */
87708 Ext.define('Ext.util.ComponentDragger', {
87709
87710     /**
87711      * @cfg {Boolean} constrain
87712      * Specify as <code>true</code> to constrain the Component to within the bounds of the {@link #constrainTo} region.
87713      */
87714
87715     /**
87716      * @cfg {String/Element} delegate
87717      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the Component's encapsulating
87718      * Element which are the drag handles. This limits dragging to only begin when the matching elements are mousedowned.</p>
87719      * <p>This may also be a specific child element within the Component's encapsulating element to use as the drag handle.</p>
87720      */
87721
87722     /**
87723      * @cfg {Boolean} constrainDelegate
87724      * Specify as <code>true</code> to constrain the drag handles within the {@link constrainTo} region.
87725      */
87726
87727     extend: 'Ext.dd.DragTracker',
87728
87729     autoStart: 500,
87730
87731     constructor: function(comp, config) {
87732         this.comp = comp;
87733         this.initialConstrainTo = config.constrainTo;
87734         this.callParent([ config ]);
87735     },
87736
87737     onStart: function(e) {
87738         var me = this,
87739             comp = me.comp;
87740
87741         // Cache the start [X, Y] array
87742         this.startPosition = comp.getPosition();
87743
87744         // If client Component has a ghost method to show a lightweight version of itself
87745         // then use that as a drag proxy unless configured to liveDrag.
87746         if (comp.ghost && !comp.liveDrag) {
87747              me.proxy = comp.ghost();
87748              me.dragTarget = me.proxy.header.el;
87749         }
87750
87751         // Set the constrainTo Region before we start dragging.
87752         if (me.constrain || me.constrainDelegate) {
87753             me.constrainTo = me.calculateConstrainRegion();
87754         }
87755     },
87756
87757     calculateConstrainRegion: function() {
87758         var me = this,
87759             comp = me.comp,
87760             c = me.initialConstrainTo,
87761             delegateRegion,
87762             elRegion,
87763             shadowSize = comp.el.shadow ? comp.el.shadow.offset : 0;
87764
87765         // The configured constrainTo might be a Region or an element
87766         if (!(c instanceof Ext.util.Region)) {
87767             c =  Ext.fly(c).getViewRegion();
87768         }
87769
87770         // Reduce the constrain region to allow for shadow
87771         if (shadowSize) {
87772             c.adjust(0, -shadowSize, -shadowSize, shadowSize);
87773         }
87774
87775         // If they only want to constrain the *delegate* to within the constrain region,
87776         // adjust the region to be larger based on the insets of the delegate from the outer
87777         // edges of the Component.
87778         if (!me.constrainDelegate) {
87779             delegateRegion = Ext.fly(me.dragTarget).getRegion();
87780             elRegion = me.proxy ? me.proxy.el.getRegion() : comp.el.getRegion();
87781
87782             c.adjust(
87783                 delegateRegion.top - elRegion.top,
87784                 delegateRegion.right - elRegion.right,
87785                 delegateRegion.bottom - elRegion.bottom,
87786                 delegateRegion.left - elRegion.left
87787             );
87788         }
87789         return c;
87790     },
87791
87792     // Move either the ghost Component or the target Component to its new position on drag
87793     onDrag: function(e) {
87794         var me = this,
87795             comp = (me.proxy && !me.comp.liveDrag) ? me.proxy : me.comp,
87796             offset = me.getOffset(me.constrain || me.constrainDelegate ? 'dragTarget' : null);
87797
87798         comp.setPosition(me.startPosition[0] + offset[0], me.startPosition[1] + offset[1]);
87799     },
87800
87801     onEnd: function(e) {
87802         if (this.proxy && !this.comp.liveDrag) {
87803             this.comp.unghost();
87804         }
87805     }
87806 });
87807 /**
87808  * @class Ext.form.Labelable
87809
87810 A mixin which allows a component to be configured and decorated with a label and/or error message as is
87811 common for form fields. This is used by e.g. {@link Ext.form.field.Base} and {@link Ext.form.FieldContainer}
87812 to let them be managed by the Field layout.
87813
87814 **NOTE**: This mixin is mainly for internal library use and most users should not need to use it directly. It
87815 is more likely you will want to use one of the component classes that import this mixin, such as
87816 {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer}.
87817
87818 Use of this mixin does not make a component a field in the logical sense, meaning it does not provide any
87819 logic or state related to values or validation; that is handled by the related {@link Ext.form.field.Field}
87820 mixin. These two mixins may be used separately (for example {@link Ext.form.FieldContainer} is Labelable but not a
87821 Field), or in combination (for example {@link Ext.form.field.Base} implements both and has logic for connecting the
87822 two.)
87823
87824 Component classes which use this mixin should use the Field layout
87825 or a derivation thereof to properly size and position the label and message according to the component config.
87826 They must also call the {@link #initLabelable} method during component initialization to ensure the mixin gets
87827 set up correctly.
87828
87829  * @markdown
87830  * @docauthor Jason Johnston <jason@sencha.com>
87831  */
87832 Ext.define("Ext.form.Labelable", {
87833     requires: ['Ext.XTemplate'],
87834
87835     /**
87836      * @cfg {Array/String/Ext.XTemplate} labelableRenderTpl
87837      * The rendering template for the field decorations. Component classes using this mixin should include
87838      * logic to use this as their {@link Ext.AbstractComponent#renderTpl renderTpl}, and implement the
87839      * {@link #getSubTplMarkup} method to generate the field body content.
87840      */
87841     labelableRenderTpl: [
87842         '<tpl if="!hideLabel && !(!fieldLabel && hideEmptyLabel)">',
87843             '<label<tpl if="inputId"> for="{inputId}"</tpl> class="{labelCls}"<tpl if="labelStyle"> style="{labelStyle}"</tpl>>',
87844                 '<tpl if="fieldLabel">{fieldLabel}{labelSeparator}</tpl>',
87845             '</label>',
87846         '</tpl>',
87847         '<div class="{baseBodyCls} {fieldBodyCls}"<tpl if="inputId"> id="{baseBodyCls}-{inputId}"</tpl> role="presentation">{subTplMarkup}</div>',
87848         '<div class="{errorMsgCls}" style="display:none"></div>',
87849         '<div class="{clearCls}" role="presentation"><!-- --></div>',
87850         {
87851             compiled: true,
87852             disableFormats: true
87853         }
87854     ],
87855
87856     /**
87857      * @cfg {Ext.XTemplate} activeErrorsTpl
87858      * The template used to format the Array of error messages passed to {@link #setActiveErrors}
87859      * into a single HTML string. By default this renders each message as an item in an unordered list.
87860      */
87861     activeErrorsTpl: [
87862         '<tpl if="errors && errors.length">',
87863             '<ul><tpl for="errors"><li<tpl if="xindex == xcount"> class="last"</tpl>>{.}</li></tpl></ul>',
87864         '</tpl>'
87865     ],
87866
87867     /**
87868      * @property isFieldLabelable
87869      * @type Boolean
87870      * Flag denoting that this object is labelable as a field. Always true.
87871      */
87872     isFieldLabelable: true,
87873
87874     /**
87875      * @cfg {String} formItemCls
87876      * A CSS class to be applied to the outermost element to denote that it is participating in the form
87877      * field layout. Defaults to 'x-form-item'.
87878      */
87879     formItemCls: Ext.baseCSSPrefix + 'form-item',
87880
87881     /**
87882      * @cfg {String} labelCls
87883      * The CSS class to be applied to the label element. Defaults to 'x-form-item-label'.
87884      */
87885     labelCls: Ext.baseCSSPrefix + 'form-item-label',
87886
87887     /**
87888      * @cfg {String} errorMsgCls
87889      * The CSS class to be applied to the error message element. Defaults to 'x-form-error-msg'.
87890      */
87891     errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',
87892
87893     /**
87894      * @cfg {String} baseBodyCls
87895      * The CSS class to be applied to the body content element. Defaults to 'x-form-item-body'.
87896      */
87897     baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',
87898
87899     /**
87900      * @cfg {String} fieldBodyCls
87901      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
87902      * Defaults to empty.
87903      */
87904     fieldBodyCls: '',
87905
87906     /**
87907      * @cfg {String} clearCls
87908      * The CSS class to be applied to the special clearing div rendered directly after the field
87909      * contents wrapper to provide field clearing (defaults to <tt>'x-clear'</tt>).
87910      */
87911     clearCls: Ext.baseCSSPrefix + 'clear',
87912
87913     /**
87914      * @cfg {String} invalidCls
87915      * The CSS class to use when marking the component invalid (defaults to 'x-form-invalid')
87916      */
87917     invalidCls : Ext.baseCSSPrefix + 'form-invalid',
87918
87919     /**
87920      * @cfg {String} fieldLabel
87921      * The label for the field. It gets appended with the {@link #labelSeparator}, and its position
87922      * and sizing is determined by the {@link #labelAlign}, {@link #labelWidth}, and {@link #labelPad}
87923      * configs. Defaults to undefined.
87924      */
87925     fieldLabel: undefined,
87926
87927     /**
87928      * @cfg {String} labelAlign
87929      * <p>Controls the position and alignment of the {@link #fieldLabel}. Valid values are:</p>
87930      * <ul>
87931      * <li><tt>"left"</tt> (the default) - The label is positioned to the left of the field, with its text
87932      * aligned to the left. Its width is determined by the {@link #labelWidth} config.</li>
87933      * <li><tt>"top"</tt> - The label is positioned above the field.</li>
87934      * <li><tt>"right"</tt> - The label is positioned to the left of the field, with its text aligned
87935      * to the right. Its width is determined by the {@link #labelWidth} config.</li>
87936      * </ul>
87937      */
87938     labelAlign : 'left',
87939
87940     /**
87941      * @cfg {Number} labelWidth
87942      * The width of the {@link #fieldLabel} in pixels. Only applicable if the {@link #labelAlign} is set
87943      * to "left" or "right". Defaults to <tt>100</tt>.
87944      */
87945     labelWidth: 100,
87946
87947     /**
87948      * @cfg {Number} labelPad
87949      * The amount of space in pixels between the {@link #fieldLabel} and the input field. Defaults to <tt>5</tt>.
87950      */
87951     labelPad : 5,
87952
87953     /**
87954      * @cfg {String} labelSeparator
87955      * Character(s) to be inserted at the end of the {@link #fieldLabel label text}.
87956      */
87957     labelSeparator : ':',
87958
87959     /**
87960      * @cfg {String} labelStyle
87961      * <p>A CSS style specification string to apply directly to this field's label. Defaults to undefined.</p>
87962      */
87963
87964     /**
87965      * @cfg {Boolean} hideLabel
87966      * <p>Set to <tt>true</tt> to completely hide the label element ({@link #fieldLabel} and {@link #labelSeparator}).
87967      * Defaults to <tt>false</tt>.</p>
87968      * <p>Also see {@link #hideEmptyLabel}, which controls whether space will be reserved for an empty fieldLabel.</p>
87969      */
87970     hideLabel: false,
87971
87972     /**
87973      * @cfg {Boolean} hideEmptyLabel
87974      * <p>When set to <tt>true</tt>, the label element ({@link #fieldLabel} and {@link #labelSeparator}) will be
87975      * automatically hidden if the {@link #fieldLabel} is empty. Setting this to <tt>false</tt> will cause the empty
87976      * label element to be rendered and space to be reserved for it; this is useful if you want a field without a label
87977      * to line up with other labeled fields in the same form. Defaults to <tt>true</tt>.</p>
87978      * <p>If you wish to unconditionall hide the label even if a non-empty fieldLabel is configured, then set
87979      * the {@link #hideLabel} config to <tt>true</tt>.</p>
87980      */
87981     hideEmptyLabel: true,
87982
87983     /**
87984      * @cfg {Boolean} preventMark
87985      * <tt>true</tt> to disable displaying any {@link #setActiveError error message} set on this object.
87986      * Defaults to <tt>false</tt>.
87987      */
87988     preventMark: false,
87989
87990     /**
87991      * @cfg {Boolean} autoFitErrors
87992      * Whether to adjust the component's body area to make room for 'side' or 'under'
87993      * {@link #msgTarget error messages}. Defaults to <tt>true</tt>.
87994      */
87995     autoFitErrors: true,
87996
87997     /**
87998      * @cfg {String} msgTarget <p>The location where the error message text should display.
87999      * Must be one of the following values:</p>
88000      * <div class="mdetail-params"><ul>
88001      * <li><code>qtip</code> Display a quick tip containing the message when the user hovers over the field. This is the default.
88002      * <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>
88003      * <li><code>title</code> Display the message in a default browser title attribute popup.</li>
88004      * <li><code>under</code> Add a block div beneath the field containing the error message.</li>
88005      * <li><code>side</code> Add an error icon to the right of the field, displaying the message in a popup on hover.</li>
88006      * <li><code>none</code> Don't display any error message. This might be useful if you are implementing custom error display.</li>
88007      * <li><code>[element id]</code> Add the error message directly to the innerHTML of the specified element.</li>
88008      * </ul></div>
88009      */
88010     msgTarget: 'qtip',
88011
88012     /**
88013      * @cfg {String} activeError
88014      * If specified, then the component will be displayed with this value as its active error when
88015      * first rendered. Defaults to undefined. Use {@link #setActiveError} or {@link #unsetActiveError} to
88016      * change it after component creation.
88017      */
88018
88019
88020     /**
88021      * Performs initialization of this mixin. Component classes using this mixin should call this method
88022      * during their own initialization.
88023      */
88024     initLabelable: function() {
88025         this.addCls(this.formItemCls);
88026
88027         this.addEvents(
88028             /**
88029              * @event errorchange
88030              * Fires when the active error message is changed via {@link #setActiveError}.
88031              * @param {Ext.form.Labelable} this
88032              * @param {String} error The active error message
88033              */
88034             'errorchange'
88035         );
88036     },
88037
88038     /**
88039      * Returns the label for the field. Defaults to simply returning the {@link #fieldLabel} config. Can be
88040      * overridden to provide
88041      * @return {String} The configured field label, or empty string if not defined
88042      */
88043     getFieldLabel: function() {
88044         return this.fieldLabel || '';
88045     },
88046
88047     /**
88048      * @protected
88049      * Generates the arguments for the field decorations {@link #labelableRenderTpl rendering template}.
88050      * @return {Object} The template arguments
88051      */
88052     getLabelableRenderData: function() {
88053         var me = this,
88054             labelAlign = me.labelAlign,
88055             labelPad = me.labelPad,
88056             labelStyle;
88057
88058         // Calculate label styles up front rather than in the Field layout for speed; this
88059         // is safe because label alignment/width/pad are not expected to change.
88060         if (labelAlign === 'top') {
88061             labelStyle = 'margin-bottom:' + labelPad + 'px;';
88062         } else {
88063             labelStyle = 'margin-right:' + labelPad + 'px;';
88064             // Add the width for border-box browsers; will be set by the Field layout for content-box
88065             if (Ext.isBorderBox) {
88066                 labelStyle += 'width:' + me.labelWidth + 'px;';
88067             }
88068         }
88069
88070         return Ext.copyTo(
88071             {
88072                 inputId: me.getInputId(),
88073                 fieldLabel: me.getFieldLabel(),
88074                 labelStyle: labelStyle + (me.labelStyle || ''),
88075                 subTplMarkup: me.getSubTplMarkup()
88076             },
88077             me,
88078             'hideLabel,hideEmptyLabel,labelCls,fieldBodyCls,baseBodyCls,errorMsgCls,clearCls,labelSeparator',
88079             true
88080         );
88081     },
88082
88083     /**
88084      * @protected
88085      * Returns the additional {@link Ext.AbstractComponent#renderSelectors} for selecting the field
88086      * decoration elements from the rendered {@link #labelableRenderTpl}. Component classes using this mixin should
88087      * be sure and merge this method's result into the component's {@link Ext.AbstractComponent#renderSelectors}
88088      * before rendering.
88089      */
88090     getLabelableSelectors: function() {
88091         return {
88092             /**
88093              * @property labelEl
88094              * @type Ext.core.Element
88095              * The label Element for this component. Only available after the component has been rendered.
88096              */
88097             labelEl: 'label.' + this.labelCls,
88098
88099             /**
88100              * @property bodyEl
88101              * @type Ext.core.Element
88102              * The div Element wrapping the component's contents. Only available after the component has been rendered.
88103              */
88104             bodyEl: '.' + this.baseBodyCls,
88105
88106             /**
88107              * @property errorEl
88108              * @type Ext.core.Element
88109              * The div Element that will contain the component's error message(s). Note that depending on the
88110              * configured {@link #msgTarget}, this element may be hidden in favor of some other form of
88111              * presentation, but will always be present in the DOM for use by assistive technologies.
88112              */
88113             errorEl: '.' + this.errorMsgCls
88114         };
88115     },
88116
88117     /**
88118      * @protected
88119      * Gets the markup to be inserted into the outer template's bodyEl. Defaults to empty string, should
88120      * be implemented by classes including this mixin as needed.
88121      * @return {String} The markup to be inserted
88122      */
88123     getSubTplMarkup: function() {
88124         return '';
88125     },
88126
88127     /**
88128      * Get the input id, if any, for this component. This is used as the "for" attribute on the label element.
88129      * Implementing subclasses may also use this as e.g. the id for their own <tt>input</tt> element.
88130      * @return {String} The input id
88131      */
88132     getInputId: function() {
88133         return '';
88134     },
88135
88136     /**
88137      * Gets the active error message for this component, if any. This does not trigger
88138      * validation on its own, it merely returns any message that the component may already hold.
88139      * @return {String} The active error message on the component; if there is no error, an empty string is returned.
88140      */
88141     getActiveError : function() {
88142         return this.activeError || '';
88143     },
88144
88145     /**
88146      * Tells whether the field currently has an active error message. This does not trigger
88147      * validation on its own, it merely looks for any message that the component may already hold.
88148      * @return {Boolean}
88149      */
88150     hasActiveError: function() {
88151         return !!this.getActiveError();
88152     },
88153
88154     /**
88155      * Sets the active error message to the given string. This replaces the entire error message
88156      * contents with the given string. Also see {@link #setActiveErrors} which accepts an Array of
88157      * messages and formats them according to the {@link #activeErrorsTpl}.
88158      * @param {String} msg The error message
88159      */
88160     setActiveError: function(msg) {
88161         this.activeError = msg;
88162         this.activeErrors = [msg];
88163         this.renderActiveError();
88164     },
88165
88166     /**
88167      * Gets an Array of any active error messages currently applied to the field. This does not trigger
88168      * validation on its own, it merely returns any messages that the component may already hold.
88169      * @return {Array} The active error messages on the component; if there are no errors, an empty Array is returned.
88170      */
88171     getActiveErrors: function() {
88172         return this.activeErrors || [];
88173     },
88174
88175     /**
88176      * Set the active error message to an Array of error messages. The messages are formatted into
88177      * a single message string using the {@link #activeErrorsTpl}. Also see {@link #setActiveError}
88178      * which allows setting the entire error contents with a single string.
88179      * @param {Array} errors The error messages
88180      */
88181     setActiveErrors: function(errors) {
88182         this.activeErrors = errors;
88183         this.activeError = this.getTpl('activeErrorsTpl').apply({errors: errors});
88184         this.renderActiveError();
88185     },
88186
88187     /**
88188      * Clears the active error.
88189      */
88190     unsetActiveError: function() {
88191         delete this.activeError;
88192         delete this.activeErrors;
88193         this.renderActiveError();
88194     },
88195
88196     /**
88197      * @private
88198      * Updates the rendered DOM to match the current activeError. This only updates the content and
88199      * attributes, you'll have to call doComponentLayout to actually update the display.
88200      */
88201     renderActiveError: function() {
88202         var me = this,
88203             activeError = me.getActiveError(),
88204             hasError = !!activeError;
88205
88206         if (activeError !== me.lastActiveError) {
88207             me.fireEvent('errorchange', me, activeError);
88208             me.lastActiveError = activeError;
88209         }
88210
88211         if (me.rendered && !me.isDestroyed && !me.preventMark) {
88212             // Add/remove invalid class
88213             me.el[hasError ? 'addCls' : 'removeCls'](me.invalidCls);
88214
88215             // Update the aria-invalid attribute
88216             me.getActionEl().dom.setAttribute('aria-invalid', hasError);
88217
88218             // Update the errorEl with the error message text
88219             me.errorEl.dom.innerHTML = activeError;
88220         }
88221     },
88222
88223     /**
88224      * Applies a set of default configuration values to this Labelable instance. For each of the
88225      * properties in the given object, check if this component hasOwnProperty that config; if not
88226      * then it's inheriting a default value from its prototype and we should apply the default value.
88227      * @param {Object} defaults The defaults to apply to the object.
88228      */
88229     setFieldDefaults: function(defaults) {
88230         var me = this;
88231         Ext.iterate(defaults, function(key, val) {
88232             if (!me.hasOwnProperty(key)) {
88233                 me[key] = val;
88234             }
88235         });
88236     },
88237
88238     /**
88239      * @protected Calculate and return the natural width of the bodyEl. Override to provide custom logic.
88240      * Note for implementors: if at all possible this method should be overridden with a custom implementation
88241      * that can avoid anything that would cause the browser to reflow, e.g. querying offsetWidth.
88242      */
88243     getBodyNaturalWidth: function() {
88244         return this.bodyEl.getWidth();
88245     }
88246
88247 });
88248
88249 /**
88250  * @class Ext.form.field.Field
88251
88252 This mixin provides a common interface for the logical behavior and state of form fields, including:
88253
88254 - Getter and setter methods for field values
88255 - Events and methods for tracking value and validity changes
88256 - Methods for triggering validation
88257
88258 **NOTE**: When implementing custom fields, it is most likely that you will want to extend the {@link Ext.form.field.Base}
88259 component class rather than using this mixin directly, as BaseField contains additional logic for generating an
88260 actual DOM complete with {@link Ext.form.Labelable label and error message} display and a form input field,
88261 plus methods that bind the Field value getters and setters to the input field's value.
88262
88263 If you do want to implement this mixin directly and don't want to extend {@link Ext.form.field.Base}, then
88264 you will most likely want to override the following methods with custom implementations: {@link #getValue},
88265 {@link #setValue}, and {@link #getErrors}. Other methods may be overridden as needed but their base
88266 implementations should be sufficient for common cases. You will also need to make sure that {@link #initField}
88267 is called during the component's initialization.
88268
88269  * @markdown
88270  * @docauthor Jason Johnston <jason@sencha.com>
88271  */
88272 Ext.define('Ext.form.field.Field', {
88273
88274     /**
88275      * @property isFormField
88276      * @type {Boolean}
88277      * Flag denoting that this component is a Field. Always true.
88278      */
88279     isFormField : true,
88280
88281     /**
88282      * @cfg {Mixed} value A value to initialize this field with (defaults to undefined).
88283      */
88284     
88285     /**
88286      * @cfg {String} name The name of the field (defaults to undefined). By default this is used as the parameter
88287      * name when including the {@link #getSubmitData field value} in a {@link Ext.form.Basic#submit form submit()}.
88288      * To prevent the field from being included in the form submit, set {@link #submitValue} to <tt>false</tt>.
88289      */
88290
88291     /**
88292      * @cfg {Boolean} disabled True to disable the field (defaults to false). Disabled Fields will not be
88293      * {@link Ext.form.Basic#submit submitted}.</p>
88294      */
88295     disabled : false,
88296
88297     /**
88298      * @cfg {Boolean} submitValue Setting this to <tt>false</tt> will prevent the field from being
88299      * {@link Ext.form.Basic#submit submitted} even when it is not disabled. Defaults to <tt>true</tt>.
88300      */
88301     submitValue: true,
88302
88303     /**
88304      * @cfg {Boolean} validateOnChange
88305      * <p>Specifies whether this field should be validated immediately whenever a change in its value is detected.
88306      * Defaults to <tt>true</tt>. If the validation results in a change in the field's validity, a
88307      * {@link #validitychange} event will be fired. This allows the field to show feedback about the
88308      * validity of its contents immediately as the user is typing.</p>
88309      * <p>When set to <tt>false</tt>, feedback will not be immediate. However the form will still be validated
88310      * before submitting if the <tt>clientValidation</tt> option to {@link Ext.form.Basic#doAction} is
88311      * enabled, or if the field or form are validated manually.</p>
88312      * <p>See also {@link Ext.form.field.Base#checkChangeEvents}for controlling how changes to the field's value are detected.</p>
88313      */
88314     validateOnChange: true,
88315
88316     /**
88317      * @private
88318      */
88319     suspendCheckChange: 0,
88320
88321     /**
88322      * Initializes this Field mixin on the current instance. Components using this mixin should call
88323      * this method during their own initialization process.
88324      */
88325     initField: function() {
88326         this.addEvents(
88327             /**
88328              * @event change
88329              * Fires when a user-initiated change is detected in the value of the field.
88330              * @param {Ext.form.field.Field} this
88331              * @param {Mixed} newValue The new value
88332              * @param {Mixed} oldValue The original value
88333              */
88334             'change',
88335             /**
88336              * @event validitychange
88337              * Fires when a change in the field's validity is detected.
88338              * @param {Ext.form.field.Field} this
88339              * @param {Boolean} isValid Whether or not the field is now valid
88340              */
88341             'validitychange',
88342             /**
88343              * @event dirtychange
88344              * Fires when a change in the field's {@link #isDirty} state is detected.
88345              * @param {Ext.form.field.Field} this
88346              * @param {Boolean} isDirty Whether or not the field is now dirty
88347              */
88348             'dirtychange'
88349         );
88350
88351         this.initValue();
88352     },
88353
88354     /**
88355      * @protected
88356      * Initializes the field's value based on the initial config.
88357      */
88358     initValue: function() {
88359         var me = this;
88360
88361         /**
88362          * @property originalValue
88363          * @type Mixed
88364          * The original value of the field as configured in the {@link #value} configuration, or as loaded by
88365          * the last form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
88366          * setting is <code>true</code>.
88367          */
88368         me.originalValue = me.lastValue = me.value;
88369
88370         // Set the initial value - prevent validation on initial set
88371         me.suspendCheckChange++;
88372         me.setValue(me.value);
88373         me.suspendCheckChange--;
88374     },
88375
88376     /**
88377      * Returns the {@link Ext.form.field.Field#name name} attribute of the field. This is used as the parameter
88378      * name when including the field value in a {@link Ext.form.Basic#submit form submit()}.
88379      * @return {String} name The field {@link Ext.form.field.Field#name name}
88380      */
88381     getName: function() {
88382         return this.name;
88383     },
88384
88385     /**
88386      * Returns the current data value of the field. The type of value returned is particular to the type of the
88387      * particular field (e.g. a Date object for {@link Ext.form.field.Date}).
88388      * @return {Mixed} value The field value
88389      */
88390     getValue: function() {
88391         return this.value;
88392     },
88393     
88394     /**
88395      * Sets a data value into the field and runs the change detection and validation.
88396      * @param {Mixed} value The value to set
88397      * @return {Ext.form.field.Field} this
88398      */
88399     setValue: function(value) {
88400         var me = this;
88401         me.value = value;
88402         me.checkChange();
88403         return me;
88404     },
88405
88406     /**
88407      * Returns whether two field {@link #getValue values} are logically equal. Field implementations may override
88408      * this to provide custom comparison logic appropriate for the particular field's data type.
88409      * @param {Mixed} value1 The first value to compare
88410      * @param {Mixed} value2 The second value to compare
88411      * @return {Boolean} True if the values are equal, false if inequal.
88412      */
88413     isEqual: function(value1, value2) {
88414         return String(value1) === String(value2);
88415     },
88416
88417     /**
88418      * <p>Returns the parameter(s) that would be included in a standard form submit for this field. Typically this
88419      * will be an object with a single name-value pair, the name being this field's {@link #getName name} and the
88420      * value being its current stringified value. More advanced field implementations may return more than one
88421      * name-value pair.</p>
88422      * <p>Note that the values returned from this method are not guaranteed to have been successfully
88423      * {@link #validate validated}.</p>
88424      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array
88425      * of strings if that particular name has multiple values. It can also return <tt>null</tt> if there are no
88426      * parameters to be submitted.
88427      */
88428     getSubmitData: function() {
88429         var me = this,
88430             data = null;
88431         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
88432             data = {};
88433             data[me.getName()] = '' + me.getValue();
88434         }
88435         return data;
88436     },
88437
88438     /**
88439      * <p>Returns the value(s) that should be saved to the {@link Ext.data.Model} instance for this field, when
88440      * {@link Ext.form.Basic#updateRecord} is called. Typically this will be an object with a single name-value
88441      * pair, the name being this field's {@link #getName name} and the value being its current data value. More
88442      * advanced field implementations may return more than one name-value pair. The returned values will be
88443      * saved to the corresponding field names in the Model.</p>
88444      * <p>Note that the values returned from this method are not guaranteed to have been successfully
88445      * {@link #validate validated}.</p>
88446      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array
88447      * of strings if that particular name has multiple values. It can also return <tt>null</tt> if there are no
88448      * parameters to be submitted.
88449      */
88450     getModelData: function() {
88451         var me = this,
88452             data = null;
88453         if (!me.disabled && !me.isFileUpload()) {
88454             data = {};
88455             data[me.getName()] = me.getValue();
88456         }
88457         return data;
88458     },
88459
88460     /**
88461      * Resets the current field value to the originally loaded value and clears any validation messages.
88462      * See {@link Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
88463      */
88464     reset : function(){
88465         var me = this;
88466         
88467         me.setValue(me.originalValue);
88468         me.clearInvalid();
88469         // delete here so we reset back to the original state
88470         delete me.wasValid;
88471     },
88472
88473     /**
88474      * Resets the field's {@link #originalValue} property so it matches the current {@link #getValue value}.
88475      * This is called by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues} if the form's
88476      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} property is set to true.
88477      */
88478     resetOriginalValue: function() {
88479         this.originalValue = this.getValue();
88480         this.checkDirty();
88481     },
88482
88483     /**
88484      * <p>Checks whether the value of the field has changed since the last time it was checked. If the value
88485      * has changed, it:</p>
88486      * <ol>
88487      * <li>Fires the {@link #change change event},</li>
88488      * <li>Performs validation if the {@link #validateOnChange} config is enabled, firing the
88489      * {@link #validationchange validationchange event} if the validity has changed, and</li>
88490      * <li>Checks the {@link #isDirty dirty state} of the field and fires the {@link #dirtychange dirtychange event}
88491      * if it has changed.</li>
88492      * </ol>
88493      */
88494     checkChange: function() {
88495         if (!this.suspendCheckChange) {
88496             var me = this,
88497                 newVal = me.getValue(),
88498                 oldVal = me.lastValue;
88499             if (!me.isEqual(newVal, oldVal) && !me.isDestroyed) {
88500                 me.lastValue = newVal;
88501                 me.fireEvent('change', me, newVal, oldVal);
88502                 me.onChange(newVal, oldVal);
88503             }
88504         }
88505     },
88506
88507     /**
88508      * @private
88509      * Called when the field's value changes. Performs validation if the {@link #validateOnChange}
88510      * config is enabled, and invokes the dirty check.
88511      */
88512     onChange: function(newVal, oldVal) {
88513         if (this.validateOnChange) {
88514             this.validate();
88515         }
88516         this.checkDirty();
88517     },
88518
88519     /**
88520      * <p>Returns true if the value of this Field has been changed from its {@link #originalValue}.
88521      * Will always return false if the field is disabled.</p>
88522      * <p>Note that if the owning {@link Ext.form.Basic form} was configured with
88523      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
88524      * then the {@link #originalValue} is updated when the values are loaded by
88525      * {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues}.</p>
88526      * @return {Boolean} True if this field has been changed from its original value (and
88527      * is not disabled), false otherwise.
88528      */
88529     isDirty : function() {
88530         var me = this;
88531         return !me.disabled && !me.isEqual(me.getValue(), me.originalValue);
88532     },
88533
88534     /**
88535      * Checks the {@link #isDirty} state of the field and if it has changed since the last time
88536      * it was checked, fires the {@link #dirtychange} event.
88537      */
88538     checkDirty: function() {
88539         var me = this,
88540             isDirty = me.isDirty();
88541         if (isDirty !== me.wasDirty) {
88542             me.fireEvent('dirtychange', me, isDirty);
88543             me.onDirtyChange(isDirty);
88544             me.wasDirty = isDirty;
88545         }
88546     },
88547
88548     /**
88549      * @private Called when the field's dirty state changes.
88550      * @param {Boolean} isDirty
88551      */
88552     onDirtyChange: Ext.emptyFn,
88553
88554     /**
88555      * <p>Runs this field's validators and returns an array of error messages for any validation failures.
88556      * This is called internally during validation and would not usually need to be used manually.</p>
88557      * <p>Each subclass should override or augment the return value to provide their own errors.</p>
88558      * @param {Mixed} value The value to get errors for (defaults to the current field value)
88559      * @return {Array} All error messages for this field; an empty Array if none.
88560      */
88561     getErrors: function(value) {
88562         return [];
88563     },
88564
88565     /**
88566      * <p>Returns whether or not the field value is currently valid by {@link #getErrors validating} the
88567      * field's current value. The {@link #validitychange} event will not be fired; use {@link #validate}
88568      * instead if you want the event to fire. <b>Note</b>: {@link #disabled} fields are always treated as valid.</p>
88569      * <p>Implementations are encouraged to ensure that this method does not have side-effects such as
88570      * triggering error message display.</p>
88571      * @return {Boolean} True if the value is valid, else false
88572      */
88573     isValid : function() {
88574         var me = this;
88575         return me.disabled || Ext.isEmpty(me.getErrors());
88576     },
88577
88578     /**
88579      * <p>Returns whether or not the field value is currently valid by {@link #getErrors validating} the
88580      * field's current value, and fires the {@link #validitychange} event if the field's validity has
88581      * changed since the last validation. <b>Note</b>: {@link #disabled} fields are always treated as valid.</p>
88582      * <p>Custom implementations of this method are allowed to have side-effects such as triggering error
88583      * message display. To validate without side-effects, use {@link #isValid}.</p>
88584      * @return {Boolean} True if the value is valid, else false
88585      */
88586     validate : function() {
88587         var me = this,
88588             isValid = me.isValid();
88589         if (isValid !== me.wasValid) {
88590             me.wasValid = isValid;
88591             me.fireEvent('validitychange', me, isValid);
88592         }
88593         return isValid;
88594     },
88595
88596     /**
88597      * A utility for grouping a set of modifications which may trigger value changes into a single
88598      * transaction, to prevent excessive firing of {@link #change} events. This is useful for instance
88599      * if the field has sub-fields which are being updated as a group; you don't want the container
88600      * field to check its own changed state for each subfield change.
88601      * @param fn A function containing the transaction code
88602      */
88603     batchChanges: function(fn) {
88604         this.suspendCheckChange++;
88605         fn();
88606         this.suspendCheckChange--;
88607         this.checkChange();
88608     },
88609
88610     /**
88611      * Returns whether this Field is a file upload field; if it returns true, forms will use
88612      * special techniques for {@link Ext.form.Basic#submit submitting the form} via AJAX. See
88613      * {@link Ext.form.Basic#hasUpload} for details. If this returns true, the {@link #extractFileInput}
88614      * method must also be implemented to return the corresponding file input element.
88615      * @return {Boolean}
88616      */
88617     isFileUpload: function() {
88618         return false;
88619     },
88620
88621     /**
88622      * Only relevant if the instance's {@link #isFileUpload} method returns true. Returns a reference
88623      * to the file input DOM element holding the user's selected file. The input will be appended into
88624      * the submission form and will not be returned, so this method should also create a replacement.
88625      * @return {HTMLInputElement}
88626      */
88627     extractFileInput: function() {
88628         return null;
88629     },
88630
88631     /**
88632      * <p>Associate one or more error messages with this field. Components using this mixin should implement
88633      * this method to update the component's rendering to display the messages.</p>
88634      * <p><b>Note</b>: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to
88635      * return <code>false</code> if the value does <i>pass</i> validation. So simply marking a Field as invalid
88636      * will not prevent submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation}
88637      * option set.</p>
88638      * @param {String/Array} errors The error message(s) for the field.
88639      * @method
88640      */
88641     markInvalid: Ext.emptyFn,
88642
88643     /**
88644      * <p>Clear any invalid styles/messages for this field. Components using this mixin should implement
88645      * this method to update the components rendering to clear any existing messages.</p>
88646      * <p><b>Note</b>: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to
88647      * return <code>true</code> if the value does not <i>pass</i> validation. So simply clearing a field's errors
88648      * will not necessarily allow submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation}
88649      * option set.</p>
88650      * @method
88651      */
88652     clearInvalid: Ext.emptyFn
88653
88654 });
88655
88656 /**
88657  * @class Ext.layout.component.field.Field
88658  * @extends Ext.layout.component.Component
88659  * Layout class for components with {@link Ext.form.Labelable field labeling}, handling the sizing and alignment of
88660  * the form control, label, and error message treatment.
88661  * @private
88662  */
88663 Ext.define('Ext.layout.component.field.Field', {
88664
88665     /* Begin Definitions */
88666
88667     alias: ['layout.field'],
88668
88669     extend: 'Ext.layout.component.Component',
88670
88671     uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics'],
88672
88673     /* End Definitions */
88674
88675     type: 'field',
88676
88677     beforeLayout: function(width, height) {
88678         var me = this;
88679         return me.callParent(arguments) || (!me.owner.preventMark && me.activeError !== me.owner.getActiveError());
88680     },
88681
88682     onLayout: function(width, height) {
88683         var me = this,
88684             owner = me.owner,
88685             labelStrategy = me.getLabelStrategy(),
88686             errorStrategy = me.getErrorStrategy(),
88687             isDefined = Ext.isDefined,
88688             isNumber = Ext.isNumber,
88689             lastSize, autoWidth, autoHeight, info, undef;
88690
88691         lastSize = me.lastComponentSize || {};
88692         if (!isDefined(width)) {
88693             width = lastSize.width;
88694             if (width < 0) { //first pass lastComponentSize.width is -Infinity
88695                 width = undef;
88696             }
88697         }
88698         if (!isDefined(height)) {
88699             height = lastSize.height;
88700             if (height < 0) { //first pass lastComponentSize.height is -Infinity
88701                 height = undef;
88702             }
88703         }
88704         autoWidth = !isNumber(width);
88705         autoHeight = !isNumber(height);
88706
88707         info = {
88708             autoWidth: autoWidth,
88709             autoHeight: autoHeight,
88710             width: autoWidth ? owner.getBodyNaturalWidth() : width, //always give a pixel width
88711             height: height,
88712             setOuterWidth: false, //whether the outer el width should be set to the calculated width
88713
88714             // insets for the bodyEl from each side of the component layout area
88715             insets: {
88716                 top: 0,
88717                 right: 0,
88718                 bottom: 0,
88719                 left: 0
88720             }
88721         };
88722
88723         // NOTE the order of calculating insets and setting styles here is very important; we must first
88724         // calculate and set horizontal layout alone, as the horizontal sizing of elements can have an impact
88725         // on the vertical sizes due to wrapping, then calculate and set the vertical layout.
88726
88727         // perform preparation on the label and error (setting css classes, qtips, etc.)
88728         labelStrategy.prepare(owner, info);
88729         errorStrategy.prepare(owner, info);
88730
88731         // calculate the horizontal insets for the label and error
88732         labelStrategy.adjustHorizInsets(owner, info);
88733         errorStrategy.adjustHorizInsets(owner, info);
88734
88735         // set horizontal styles for label and error based on the current insets
88736         labelStrategy.layoutHoriz(owner, info);
88737         errorStrategy.layoutHoriz(owner, info);
88738
88739         // calculate the vertical insets for the label and error
88740         labelStrategy.adjustVertInsets(owner, info);
88741         errorStrategy.adjustVertInsets(owner, info);
88742
88743         // set vertical styles for label and error based on the current insets
88744         labelStrategy.layoutVert(owner, info);
88745         errorStrategy.layoutVert(owner, info);
88746
88747         // perform sizing of the elements based on the final dimensions and insets
88748         if (autoWidth && autoHeight) {
88749             // Don't use setTargetSize if auto-sized, so the calculated size is not reused next time
88750             me.setElementSize(owner.el, (info.setOuterWidth ? info.width : undef), info.height);
88751         } else {
88752             me.setTargetSize((!autoWidth || info.setOuterWidth ? info.width : undef), info.height);
88753         }
88754         me.sizeBody(info);
88755
88756         me.activeError = owner.getActiveError();
88757     },
88758
88759
88760     /**
88761      * Perform sizing and alignment of the bodyEl (and children) to match the calculated insets.
88762      */
88763     sizeBody: function(info) {
88764         var me = this,
88765             owner = me.owner,
88766             insets = info.insets,
88767             totalWidth = info.width,
88768             totalHeight = info.height,
88769             width = Ext.isNumber(totalWidth) ? totalWidth - insets.left - insets.right : totalWidth,
88770             height = Ext.isNumber(totalHeight) ? totalHeight - insets.top - insets.bottom : totalHeight;
88771
88772         // size the bodyEl
88773         me.setElementSize(owner.bodyEl, width, height);
88774
88775         // size the bodyEl's inner contents if necessary
88776         me.sizeBodyContents(width, height);
88777     },
88778
88779     /**
88780      * Size the contents of the field body, given the full dimensions of the bodyEl. Does nothing by
88781      * default, subclasses can override to handle their specific contents.
88782      * @param {Number} width The bodyEl width
88783      * @param {Number} height The bodyEl height
88784      */
88785     sizeBodyContents: Ext.emptyFn,
88786
88787
88788     /**
88789      * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
88790      * that is appropriate for the field's {@link Ext.form.field.Field#labelAlign labelAlign} config.
88791      */
88792     getLabelStrategy: function() {
88793         var me = this,
88794             strategies = me.labelStrategies,
88795             labelAlign = me.owner.labelAlign;
88796         return strategies[labelAlign] || strategies.base;
88797     },
88798
88799     /**
88800      * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
88801      * that is appropriate for the field's {@link Ext.form.field.Field#msgTarget msgTarget} config.
88802      */
88803     getErrorStrategy: function() {
88804         var me = this,
88805             owner = me.owner,
88806             strategies = me.errorStrategies,
88807             msgTarget = owner.msgTarget;
88808         return !owner.preventMark && Ext.isString(msgTarget) ?
88809                 (strategies[msgTarget] || strategies.elementId) :
88810                 strategies.none;
88811     },
88812
88813
88814
88815     /**
88816      * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
88817      * An appropriate one will be chosen based on the owner field's {@link Ext.form.field.Field#labelAlign} config.
88818      */
88819     labelStrategies: (function() {
88820         var applyIf = Ext.applyIf,
88821             emptyFn = Ext.emptyFn,
88822             base = {
88823                 prepare: function(owner, info) {
88824                     var cls = owner.labelCls + '-' + owner.labelAlign,
88825                         labelEl = owner.labelEl;
88826                     if (labelEl && !labelEl.hasCls(cls)) {
88827                         labelEl.addCls(cls);
88828                     }
88829                 },
88830                 adjustHorizInsets: emptyFn,
88831                 adjustVertInsets: emptyFn,
88832                 layoutHoriz: emptyFn,
88833                 layoutVert: emptyFn
88834             },
88835             left = applyIf({
88836                 prepare: function(owner, info) {
88837                     base.prepare(owner, info);
88838                     // If auto width, add the label width to the body's natural width.
88839                     if (info.autoWidth) {
88840                         info.width += (!owner.labelEl ? 0 : owner.labelWidth + owner.labelPad);
88841                     }
88842                     // Must set outer width to prevent field from wrapping below floated label
88843                     info.setOuterWidth = true;
88844                 },
88845                 adjustHorizInsets: function(owner, info) {
88846                     if (owner.labelEl) {
88847                         info.insets.left += owner.labelWidth + owner.labelPad;
88848                     }
88849                 },
88850                 layoutHoriz: function(owner, info) {
88851                     // For content-box browsers we can't rely on Labelable.js#getLabelableRenderData
88852                     // setting the width style because it needs to account for the final calculated
88853                     // padding/border styles for the label. So we set the width programmatically here to
88854                     // normalize content-box sizing, while letting border-box browsers use the original
88855                     // width style.
88856                     var labelEl = owner.labelEl;
88857                     if (labelEl && !owner.isLabelSized && !Ext.isBorderBox) {
88858                         labelEl.setWidth(owner.labelWidth);
88859                         owner.isLabelSized = true;
88860                     }
88861                 }
88862             }, base);
88863
88864
88865         return {
88866             base: base,
88867
88868             /**
88869              * Label displayed above the bodyEl
88870              */
88871             top: applyIf({
88872                 adjustVertInsets: function(owner, info) {
88873                     var labelEl = owner.labelEl;
88874                     if (labelEl) {
88875                         info.insets.top += Ext.util.TextMetrics.measure(labelEl, owner.fieldLabel, info.width).height +
88876                                            labelEl.getFrameWidth('tb') + owner.labelPad;
88877                     }
88878                 }
88879             }, base),
88880
88881             /**
88882              * Label displayed to the left of the bodyEl
88883              */
88884             left: left,
88885
88886             /**
88887              * Same as left, only difference is text-align in CSS
88888              */
88889             right: left
88890         };
88891     })(),
88892
88893
88894
88895     /**
88896      * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
88897      * An appropriate one will be chosen based on the owner field's {@link Ext.form.field.Field#msgTarget} config.
88898      */
88899     errorStrategies: (function() {
88900         function setDisplayed(el, displayed) {
88901             var wasDisplayed = el.getStyle('display') !== 'none';
88902             if (displayed !== wasDisplayed) {
88903                 el.setDisplayed(displayed);
88904             }
88905         }
88906
88907         function setStyle(el, name, value) {
88908             if (el.getStyle(name) !== value) {
88909                 el.setStyle(name, value);
88910             }
88911         }
88912
88913         var applyIf = Ext.applyIf,
88914             emptyFn = Ext.emptyFn,
88915             base = {
88916                 prepare: function(owner) {
88917                     setDisplayed(owner.errorEl, false);
88918                 },
88919                 adjustHorizInsets: emptyFn,
88920                 adjustVertInsets: emptyFn,
88921                 layoutHoriz: emptyFn,
88922                 layoutVert: emptyFn
88923             };
88924
88925         return {
88926             none: base,
88927
88928             /**
88929              * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
88930              */
88931             side: applyIf({
88932                 prepare: function(owner) {
88933                     var errorEl = owner.errorEl;
88934                     errorEl.addCls(Ext.baseCSSPrefix + 'form-invalid-icon');
88935                     Ext.layout.component.field.Field.initTip();
88936                     errorEl.dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
88937                     setDisplayed(errorEl, owner.hasActiveError());
88938                 },
88939                 adjustHorizInsets: function(owner, info) {
88940                     if (owner.autoFitErrors && owner.hasActiveError()) {
88941                         info.insets.right += owner.errorEl.getWidth();
88942                     }
88943                 },
88944                 layoutHoriz: function(owner, info) {
88945                     if (owner.hasActiveError()) {
88946                         setStyle(owner.errorEl, 'left', info.width - info.insets.right + 'px');
88947                     }
88948                 },
88949                 layoutVert: function(owner, info) {
88950                     if (owner.hasActiveError()) {
88951                         setStyle(owner.errorEl, 'top', info.insets.top + 'px');
88952                     }
88953                 }
88954             }, base),
88955
88956             /**
88957              * Error message displayed underneath the bodyEl
88958              */
88959             under: applyIf({
88960                 prepare: function(owner) {
88961                     var errorEl = owner.errorEl,
88962                         cls = Ext.baseCSSPrefix + 'form-invalid-under';
88963                     if (!errorEl.hasCls(cls)) {
88964                         errorEl.addCls(cls);
88965                     }
88966                     setDisplayed(errorEl, owner.hasActiveError());
88967                 },
88968                 adjustVertInsets: function(owner, info) {
88969                     if (owner.autoFitErrors) {
88970                         info.insets.bottom += owner.errorEl.getHeight();
88971                     }
88972                 },
88973                 layoutHoriz: function(owner, info) {
88974                     var errorEl = owner.errorEl,
88975                         insets = info.insets;
88976
88977                     setStyle(errorEl, 'width', info.width - insets.right - insets.left + 'px');
88978                     setStyle(errorEl, 'marginLeft', insets.left + 'px');
88979                 }
88980             }, base),
88981
88982             /**
88983              * Error displayed as QuickTip on hover of the field container
88984              */
88985             qtip: applyIf({
88986                 prepare: function(owner) {
88987                     setDisplayed(owner.errorEl, false);
88988                     Ext.layout.component.field.Field.initTip();
88989                     owner.getActionEl().dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
88990                 }
88991             }, base),
88992
88993             /**
88994              * Error displayed as title tip on hover of the field container
88995              */
88996             title: applyIf({
88997                 prepare: function(owner) {
88998                     setDisplayed(owner.errorEl, false);
88999                     owner.el.dom.title = owner.getActiveError() || '';
89000                 }
89001             }, base),
89002
89003             /**
89004              * Error message displayed as content of an element with a given id elsewhere in the app
89005              */
89006             elementId: applyIf({
89007                 prepare: function(owner) {
89008                     setDisplayed(owner.errorEl, false);
89009                     var targetEl = Ext.fly(owner.msgTarget);
89010                     if (targetEl) {
89011                         targetEl.dom.innerHTML = owner.getActiveError() || '';
89012                         targetEl.setDisplayed(owner.hasActiveError());
89013                     }
89014                 }
89015             }, base)
89016         };
89017     })(),
89018
89019     statics: {
89020         /**
89021          * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
89022          * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
89023          */
89024         initTip: function() {
89025             var tip = this.tip;
89026             if (!tip) {
89027                 tip = this.tip = Ext.create('Ext.tip.QuickTip', {
89028                     baseCls: Ext.baseCSSPrefix + 'form-invalid-tip',
89029                     renderTo: Ext.getBody()
89030                 });
89031                 tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
89032             }
89033         },
89034
89035         /**
89036          * Destroy the error tip instance.
89037          */
89038         destroyTip: function() {
89039             var tip = this.tip;
89040             if (tip) {
89041                 tip.destroy();
89042                 delete this.tip;
89043             }
89044         }
89045     }
89046
89047 });
89048
89049 /**
89050  * @class Ext.form.field.VTypes
89051  * <p>This is a singleton object which contains a set of commonly used field validation functions.
89052  * The validations provided are basic and intended to be easily customizable and extended.</p>
89053  * <p>To add custom VTypes specify the <code>{@link Ext.form.field.Text#vtype vtype}</code> validation
89054  * test function, and optionally specify any corresponding error text to display and any keystroke
89055  * filtering mask to apply. For example:</p>
89056  * <pre><code>
89057 // custom Vtype for vtype:'time'
89058 var timeTest = /^([1-9]|1[0-9]):([0-5][0-9])(\s[a|p]m)$/i;
89059 Ext.apply(Ext.form.field.VTypes, {
89060     //  vtype validation function
89061     time: function(val, field) {
89062         return timeTest.test(val);
89063     },
89064     // vtype Text property: The error text to display when the validation function returns false
89065     timeText: 'Not a valid time.  Must be in the format "12:34 PM".',
89066     // vtype Mask property: The keystroke filter mask
89067     timeMask: /[\d\s:amp]/i
89068 });
89069  * </code></pre>
89070  * Another example:
89071  * <pre><code>
89072 // custom Vtype for vtype:'IPAddress'
89073 Ext.apply(Ext.form.field.VTypes, {
89074     IPAddress:  function(v) {
89075         return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
89076     },
89077     IPAddressText: 'Must be a numeric IP address',
89078     IPAddressMask: /[\d\.]/i
89079 });
89080  * </code></pre>
89081  * @singleton
89082  */
89083 Ext.define('Ext.form.field.VTypes', (function(){
89084     // closure these in so they are only created once.
89085     var alpha = /^[a-zA-Z_]+$/,
89086         alphanum = /^[a-zA-Z0-9_]+$/,
89087         email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
89088         url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
89089
89090     // All these messages and functions are configurable
89091     return {
89092         singleton: true,
89093         alternateClassName: 'Ext.form.VTypes',
89094
89095         /**
89096          * The function used to validate email addresses.  Note that this is a very basic validation -- complete
89097          * validation per the email RFC specifications is very complex and beyond the scope of this class, although
89098          * this function can be overridden if a more comprehensive validation scheme is desired.  See the validation
89099          * section of the <a href="http://en.wikipedia.org/wiki/E-mail_address">Wikipedia article on email addresses</a>
89100          * for additional information.  This implementation is intended to validate the following emails:<tt>
89101          * 'barney@example.de', 'barney.rubble@example.com', 'barney-rubble@example.coop', 'barney+rubble@example.com'
89102          * </tt>.
89103          * @param {String} value The email address
89104          * @return {Boolean} true if the RegExp test passed, and false if not.
89105          */
89106         'email' : function(v){
89107             return email.test(v);
89108         },
89109         /**
89110          * The error text to display when the email validation function returns false.  Defaults to:
89111          * <tt>'This field should be an e-mail address in the format "user@example.com"'</tt>
89112          * @type String
89113          */
89114         'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
89115         /**
89116          * The keystroke filter mask to be applied on email input.  See the {@link #email} method for
89117          * information about more complex email validation. Defaults to:
89118          * <tt>/[a-z0-9_\.\-@]/i</tt>
89119          * @type RegExp
89120          */
89121         'emailMask' : /[a-z0-9_\.\-@\+]/i,
89122
89123         /**
89124          * The function used to validate URLs
89125          * @param {String} value The URL
89126          * @return {Boolean} true if the RegExp test passed, and false if not.
89127          */
89128         'url' : function(v){
89129             return url.test(v);
89130         },
89131         /**
89132          * The error text to display when the url validation function returns false.  Defaults to:
89133          * <tt>'This field should be a URL in the format "http:/'+'/www.example.com"'</tt>
89134          * @type String
89135          */
89136         'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
89137
89138         /**
89139          * The function used to validate alpha values
89140          * @param {String} value The value
89141          * @return {Boolean} true if the RegExp test passed, and false if not.
89142          */
89143         'alpha' : function(v){
89144             return alpha.test(v);
89145         },
89146         /**
89147          * The error text to display when the alpha validation function returns false.  Defaults to:
89148          * <tt>'This field should only contain letters and _'</tt>
89149          * @type String
89150          */
89151         'alphaText' : 'This field should only contain letters and _',
89152         /**
89153          * The keystroke filter mask to be applied on alpha input.  Defaults to:
89154          * <tt>/[a-z_]/i</tt>
89155          * @type RegExp
89156          */
89157         'alphaMask' : /[a-z_]/i,
89158
89159         /**
89160          * The function used to validate alphanumeric values
89161          * @param {String} value The value
89162          * @return {Boolean} true if the RegExp test passed, and false if not.
89163          */
89164         'alphanum' : function(v){
89165             return alphanum.test(v);
89166         },
89167         /**
89168          * The error text to display when the alphanumeric validation function returns false.  Defaults to:
89169          * <tt>'This field should only contain letters, numbers and _'</tt>
89170          * @type String
89171          */
89172         'alphanumText' : 'This field should only contain letters, numbers and _',
89173         /**
89174          * The keystroke filter mask to be applied on alphanumeric input.  Defaults to:
89175          * <tt>/[a-z0-9_]/i</tt>
89176          * @type RegExp
89177          */
89178         'alphanumMask' : /[a-z0-9_]/i
89179     };
89180 })());
89181
89182 /**
89183  * @private
89184  * @class Ext.layout.component.field.Text
89185  * @extends Ext.layout.component.field.Field
89186  * Layout class for {@link Ext.form.field.Text} fields. Handles sizing the input field.
89187  */
89188 Ext.define('Ext.layout.component.field.Text', {
89189     extend: 'Ext.layout.component.field.Field',
89190     alias: 'layout.textfield',
89191     requires: ['Ext.util.TextMetrics'],
89192
89193     type: 'textfield',
89194
89195
89196     /**
89197      * Allow layout to proceed if the {@link Ext.form.field.Text#grow} config is enabled and the value has
89198      * changed since the last layout.
89199      */
89200     beforeLayout: function(width, height) {
89201         var me = this,
89202             owner = me.owner,
89203             lastValue = this.lastValue,
89204             value = owner.getRawValue();
89205         this.lastValue = value;
89206         return me.callParent(arguments) || (owner.grow && value !== lastValue);
89207     },
89208
89209
89210     /**
89211      * Size the field body contents given the total dimensions of the bodyEl, taking into account the optional
89212      * {@link Ext.form.field.Text#grow} configurations.
89213      * @param {Number} width The bodyEl width
89214      * @param {Number} height The bodyEl height
89215      */
89216     sizeBodyContents: function(width, height) {
89217         var size = this.adjustForGrow(width, height);
89218         this.setElementSize(this.owner.inputEl, size[0], size[1]);
89219     },
89220
89221
89222     /**
89223      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
89224      * size based on the text field's {@link Ext.form.field.Text#grow grow config}.
89225      * @param {Number} width The bodyEl width
89226      * @param {Number} height The bodyEl height
89227      * @return {Array} [inputElWidth, inputElHeight]
89228      */
89229     adjustForGrow: function(width, height) {
89230         var me = this,
89231             owner = me.owner,
89232             inputEl, value, calcWidth,
89233             result = [width, height];
89234
89235         if (owner.grow) {
89236             inputEl = owner.inputEl;
89237
89238             // Find the width that contains the whole text value
89239             value = (inputEl.dom.value || (owner.hasFocus ? '' : owner.emptyText) || '') + owner.growAppend;
89240             calcWidth = inputEl.getTextWidth(value) + inputEl.getBorderWidth("lr") + inputEl.getPadding("lr");
89241
89242             // Constrain
89243             result[0] = Ext.Number.constrain(calcWidth, owner.growMin,
89244                     Math.max(owner.growMin, Math.min(owner.growMax, Ext.isNumber(width) ? width : Infinity)));
89245         }
89246
89247         return result;
89248     }
89249
89250 });
89251
89252 /**
89253  * @private
89254  * @class Ext.layout.component.field.TextArea
89255  * @extends Ext.layout.component.field.Field
89256  * Layout class for {@link Ext.form.field.TextArea} fields. Handles sizing the textarea field.
89257  */
89258 Ext.define('Ext.layout.component.field.TextArea', {
89259     extend: 'Ext.layout.component.field.Text',
89260     alias: 'layout.textareafield',
89261
89262     type: 'textareafield',
89263
89264
89265     /**
89266      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
89267      * size based on the text field's {@link Ext.form.field.Text#grow grow config}. Overrides the
89268      * textfield layout's implementation to handle height rather than width.
89269      * @param {Number} width The bodyEl width
89270      * @param {Number} height The bodyEl height
89271      * @return {Array} [inputElWidth, inputElHeight]
89272      */
89273     adjustForGrow: function(width, height) {
89274         var me = this,
89275             owner = me.owner,
89276             inputEl, value, max,
89277             curWidth, curHeight, calcHeight,
89278             result = [width, height];
89279
89280         if (owner.grow) {
89281             inputEl = owner.inputEl;
89282             curWidth = inputEl.getWidth(true); //subtract border/padding to get the available width for the text
89283             curHeight = inputEl.getHeight();
89284
89285             // Get and normalize the field value for measurement
89286             value = inputEl.dom.value || '&#160;';
89287             value += owner.growAppend;
89288
89289             // Translate newlines to <br> tags
89290             value = value.replace(/\n/g, '<br>');
89291
89292             // Find the height that contains the whole text value
89293             calcHeight = Ext.util.TextMetrics.measure(inputEl, value, curWidth).height +
89294                          inputEl.getBorderWidth("tb") + inputEl.getPadding("tb");
89295
89296             // Constrain
89297             max = owner.growMax;
89298             if (Ext.isNumber(height)) {
89299                 max = Math.min(max, height);
89300             }
89301             result[1] = Ext.Number.constrain(calcHeight, owner.growMin, max);
89302         }
89303
89304         return result;
89305     }
89306
89307 });
89308 /**
89309  * @class Ext.layout.container.Anchor
89310  * @extends Ext.layout.container.Container
89311  * 
89312  * This is a layout that enables anchoring of contained elements relative to the container's dimensions.
89313  * If the container is resized, all anchored items are automatically rerendered according to their
89314  * <b><tt>{@link #anchor}</tt></b> rules.
89315  *
89316  * This class is intended to be extended or created via the layout: 'anchor' {@link Ext.layout.container.AbstractContainer#layout}
89317  * config, and should generally not need to be created directly via the new keyword.</p>
89318  * 
89319  * AnchorLayout does not have any direct config options (other than inherited ones). By default,
89320  * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
89321  * container using the AnchorLayout can supply an anchoring-specific config property of <b>anchorSize</b>.
89322  * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
89323  * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
89324  * logic if necessary.  
89325  *
89326  * {@img Ext.layout.container.Anchor/Ext.layout.container.Anchor.png Ext.layout.container.Anchor container layout}
89327  *
89328  * For example:
89329  *     Ext.create('Ext.Panel', {
89330  *         width: 500,
89331  *         height: 400,
89332  *         title: "AnchorLayout Panel",
89333  *         layout: 'anchor',
89334  *         renderTo: Ext.getBody(),
89335  *         items: [{
89336  *             xtype: 'panel',
89337  *             title: '75% Width and 20% Height',
89338  *             anchor: '75% 20%'
89339  *         },{
89340  *             xtype: 'panel',
89341  *             title: 'Offset -300 Width & -200 Height',
89342  *             anchor: '-300 -200'              
89343  *         },{
89344  *             xtype: 'panel',
89345  *             title: 'Mixed Offset and Percent',
89346  *             anchor: '-250 20%'
89347  *         }]
89348  *     });
89349  */
89350
89351 Ext.define('Ext.layout.container.Anchor', {
89352
89353     /* Begin Definitions */
89354
89355     alias: 'layout.anchor',
89356     extend: 'Ext.layout.container.Container',
89357     alternateClassName: 'Ext.layout.AnchorLayout',
89358
89359     /* End Definitions */
89360
89361     /**
89362      * @cfg {String} anchor
89363      * <p>This configuation option is to be applied to <b>child <tt>items</tt></b> of a container managed by
89364      * this layout (ie. configured with <tt>layout:'anchor'</tt>).</p><br/>
89365      *
89366      * <p>This value is what tells the layout how an item should be anchored to the container. <tt>items</tt>
89367      * added to an AnchorLayout accept an anchoring-specific config property of <b>anchor</b> which is a string
89368      * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
89369      * The following types of anchor values are supported:<div class="mdetail-params"><ul>
89370      *
89371      * <li><b>Percentage</b> : Any value between 1 and 100, expressed as a percentage.<div class="sub-desc">
89372      * The first anchor is the percentage width that the item should take up within the container, and the
89373      * second is the percentage height.  For example:<pre><code>
89374 // two values specified
89375 anchor: '100% 50%' // render item complete width of the container and
89376                    // 1/2 height of the container
89377 // one value specified
89378 anchor: '100%'     // the width value; the height will default to auto
89379      * </code></pre></div></li>
89380      *
89381      * <li><b>Offsets</b> : Any positive or negative integer value.<div class="sub-desc">
89382      * This is a raw adjustment where the first anchor is the offset from the right edge of the container,
89383      * and the second is the offset from the bottom edge. For example:<pre><code>
89384 // two values specified
89385 anchor: '-50 -100' // render item the complete width of the container
89386                    // minus 50 pixels and
89387                    // the complete height minus 100 pixels.
89388 // one value specified
89389 anchor: '-50'      // anchor value is assumed to be the right offset value
89390                    // bottom offset will default to 0
89391      * </code></pre></div></li>
89392      *
89393      * <li><b>Sides</b> : Valid values are <tt>'right'</tt> (or <tt>'r'</tt>) and <tt>'bottom'</tt>
89394      * (or <tt>'b'</tt>).<div class="sub-desc">
89395      * Either the container must have a fixed size or an anchorSize config value defined at render time in
89396      * order for these to have any effect.</div></li>
89397      *
89398      * <li><b>Mixed</b> : <div class="sub-desc">
89399      * Anchor values can also be mixed as needed.  For example, to render the width offset from the container
89400      * right edge by 50 pixels and 75% of the container's height use:
89401      * <pre><code>
89402 anchor: '-50 75%'
89403      * </code></pre></div></li>
89404      *
89405      *
89406      * </ul></div>
89407      */
89408
89409     type: 'anchor',
89410
89411     /**
89412      * @cfg {String} defaultAnchor
89413      *
89414      * default anchor for all child container items applied if no anchor or specific width is set on the child item.  Defaults to '100%'.
89415      *
89416      */
89417     defaultAnchor: '100%',
89418
89419     parseAnchorRE: /^(r|right|b|bottom)$/i,
89420
89421     // private
89422     onLayout: function() {
89423         this.callParent(arguments);
89424
89425         var me = this,
89426             size = me.getLayoutTargetSize(),
89427             owner = me.owner,
89428             target = me.getTarget(),
89429             ownerWidth = size.width,
89430             ownerHeight = size.height,
89431             overflow = target.getStyle('overflow'),
89432             components = me.getVisibleItems(owner),
89433             len = components.length,
89434             boxes = [],
89435             box, newTargetSize, anchorWidth, anchorHeight, component, anchorSpec, calcWidth, calcHeight,
89436             anchorsArray, anchor, i, el, cleaner;
89437
89438         if (ownerWidth < 20 && ownerHeight < 20) {
89439             return;
89440         }
89441
89442         // Anchor layout uses natural HTML flow to arrange the child items.
89443         // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
89444         // containing element height, we create a zero-sized element with style clear:both to force a "new line"
89445         if (!me.clearEl) {
89446             me.clearEl = target.createChild({
89447                 cls: Ext.baseCSSPrefix + 'clear',
89448                 role: 'presentation'
89449             });
89450         }
89451
89452         // find the container anchoring size
89453         if (owner.anchorSize) {
89454             if (typeof owner.anchorSize == 'number') {
89455                 anchorWidth = owner.anchorSize;
89456             }
89457             else {
89458                 anchorWidth = owner.anchorSize.width;
89459                 anchorHeight = owner.anchorSize.height;
89460             }
89461         }
89462         else {
89463             anchorWidth = owner.initialConfig.width;
89464             anchorHeight = owner.initialConfig.height;
89465         }
89466
89467         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
89468         if (!Ext.supports.RightMargin) {
89469             cleaner = Ext.core.Element.getRightMarginFixCleaner(target);
89470             target.addCls(Ext.baseCSSPrefix + 'inline-children');
89471         }
89472
89473         for (i = 0; i < len; i++) {
89474             component = components[i];
89475             el = component.el;
89476             anchor = component.anchor;
89477
89478             if (!component.anchor && component.items && !Ext.isNumber(component.width) && !(Ext.isIE6 && Ext.isStrict)) {
89479                 component.anchor = anchor = me.defaultAnchor;
89480             }
89481
89482             if (anchor) {
89483                 anchorSpec = component.anchorSpec;
89484                 // cache all anchor values
89485                 if (!anchorSpec) {
89486                     anchorsArray = anchor.split(' ');
89487                     component.anchorSpec = anchorSpec = {
89488                         right: me.parseAnchor(anchorsArray[0], component.initialConfig.width, anchorWidth),
89489                         bottom: me.parseAnchor(anchorsArray[1], component.initialConfig.height, anchorHeight)
89490                     };
89491                 }
89492                 calcWidth = anchorSpec.right ? me.adjustWidthAnchor(anchorSpec.right(ownerWidth) - el.getMargin('lr'), component) : undefined;
89493                 calcHeight = anchorSpec.bottom ? me.adjustHeightAnchor(anchorSpec.bottom(ownerHeight) - el.getMargin('tb'), component) : undefined;
89494
89495                 boxes.push({
89496                     component: component,
89497                     anchor: true,
89498                     width: calcWidth || undefined,
89499                     height: calcHeight || undefined
89500                 });
89501             } else {
89502                 boxes.push({
89503                     component: component,
89504                     anchor: false
89505                 });
89506             }
89507         }
89508
89509         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
89510         if (!Ext.supports.RightMargin) {
89511             target.removeCls(Ext.baseCSSPrefix + 'inline-children');
89512             cleaner();
89513         }
89514
89515         for (i = 0; i < len; i++) {
89516             box = boxes[i];
89517             me.setItemSize(box.component, box.width, box.height);
89518         }
89519
89520         if (overflow && overflow != 'hidden' && !me.adjustmentPass) {
89521             newTargetSize = me.getLayoutTargetSize();
89522             if (newTargetSize.width != size.width || newTargetSize.height != size.height) {
89523                 me.adjustmentPass = true;
89524                 me.onLayout();
89525             }
89526         }
89527
89528         delete me.adjustmentPass;
89529     },
89530
89531     // private
89532     parseAnchor: function(a, start, cstart) {
89533         if (a && a != 'none') {
89534             var ratio;
89535             // standard anchor
89536             if (this.parseAnchorRE.test(a)) {
89537                 var diff = cstart - start;
89538                 return function(v) {
89539                     return v - diff;
89540                 };
89541             }    
89542             // percentage
89543             else if (a.indexOf('%') != -1) {
89544                 ratio = parseFloat(a.replace('%', '')) * 0.01;
89545                 return function(v) {
89546                     return Math.floor(v * ratio);
89547                 };
89548             }    
89549             // simple offset adjustment
89550             else {
89551                 a = parseInt(a, 10);
89552                 if (!isNaN(a)) {
89553                     return function(v) {
89554                         return v + a;
89555                     };
89556                 }
89557             }
89558         }
89559         return null;
89560     },
89561
89562     // private
89563     adjustWidthAnchor: function(value, comp) {
89564         return value;
89565     },
89566
89567     // private
89568     adjustHeightAnchor: function(value, comp) {
89569         return value;
89570     }
89571
89572 });
89573 /**
89574  * @class Ext.form.action.Load
89575  * @extends Ext.form.action.Action
89576  * <p>A class which handles loading of data from a server into the Fields of an {@link Ext.form.Basic}.</p>
89577  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
89578  * {@link Ext.form.Basic#load load}ing.</p>
89579  * <p><u><b>Response Packet Criteria</b></u></p>
89580  * <p>A response packet <b>must</b> contain:
89581  * <div class="mdetail-params"><ul>
89582  * <li><b><code>success</code></b> property : Boolean</li>
89583  * <li><b><code>data</code></b> property : Object</li>
89584  * <div class="sub-desc">The <code>data</code> property contains the values of Fields to load.
89585  * The individual value object for each Field is passed to the Field's
89586  * {@link Ext.form.field.Field#setValue setValue} method.</div></li>
89587  * </ul></div>
89588  * <p><u><b>JSON Packets</b></u></p>
89589  * <p>By default, response packets are assumed to be JSON, so for the following form load call:<pre><code>
89590 var myFormPanel = new Ext.form.Panel({
89591     title: 'Client and routing info',
89592     items: [{
89593         fieldLabel: 'Client',
89594         name: 'clientName'
89595     }, {
89596         fieldLabel: 'Port of loading',
89597         name: 'portOfLoading'
89598     }, {
89599         fieldLabel: 'Port of discharge',
89600         name: 'portOfDischarge'
89601     }]
89602 });
89603 myFormPanel.{@link Ext.form.Panel#getForm getForm}().{@link Ext.form.Basic#load load}({
89604     url: '/getRoutingInfo.php',
89605     params: {
89606         consignmentRef: myConsignmentRef
89607     },
89608     failure: function(form, action) {
89609         Ext.Msg.alert("Load failed", action.result.errorMessage);
89610     }
89611 });
89612 </code></pre>
89613  * a <b>success response</b> packet may look like this:</p><pre><code>
89614 {
89615     success: true,
89616     data: {
89617         clientName: "Fred. Olsen Lines",
89618         portOfLoading: "FXT",
89619         portOfDischarge: "OSL"
89620     }
89621 }</code></pre>
89622  * while a <b>failure response</b> packet may look like this:</p><pre><code>
89623 {
89624     success: false,
89625     errorMessage: "Consignment reference not found"
89626 }</code></pre>
89627  * <p>Other data may be placed into the response for processing the {@link Ext.form.Basic Form}'s
89628  * callback or event handler methods. The object decoded from this JSON is available in the
89629  * {@link Ext.form.action.Action#result result} property.</p>
89630  */
89631 Ext.define('Ext.form.action.Load', {
89632     extend:'Ext.form.action.Action',
89633     requires: ['Ext.data.Connection'],
89634     alternateClassName: 'Ext.form.Action.Load',
89635     alias: 'formaction.load',
89636
89637     type: 'load',
89638
89639     /**
89640      * @private
89641      */
89642     run: function() {
89643         Ext.Ajax.request(Ext.apply(
89644             this.createCallback(),
89645             {
89646                 method: this.getMethod(),
89647                 url: this.getUrl(),
89648                 headers: this.headers,
89649                 params: this.getParams()
89650             }
89651         ));
89652     },
89653
89654     /**
89655      * @private
89656      */
89657     onSuccess: function(response){
89658         var result = this.processResponse(response),
89659             form = this.form;
89660         if (result === true || !result.success || !result.data) {
89661             this.failureType = Ext.form.action.Action.LOAD_FAILURE;
89662             form.afterAction(this, false);
89663             return;
89664         }
89665         form.clearInvalid();
89666         form.setValues(result.data);
89667         form.afterAction(this, true);
89668     },
89669
89670     /**
89671      * @private
89672      */
89673     handleResponse: function(response) {
89674         var reader = this.form.reader,
89675             rs, data;
89676         if (reader) {
89677             rs = reader.read(response);
89678             data = rs.records && rs.records[0] ? rs.records[0].data : null;
89679             return {
89680                 success : rs.success,
89681                 data : data
89682             };
89683         }
89684         return Ext.decode(response.responseText);
89685     }
89686 });
89687
89688
89689 /**
89690  * @class Ext.window.Window
89691  * @extends Ext.panel.Panel
89692  * <p>A specialized panel intended for use as an application window.  Windows are floated, {@link #resizable}, and
89693  * {@link #draggable} by default.  Windows can be {@link #maximizable maximized} to fill the viewport,
89694  * restored to their prior size, and can be {@link #minimize}d.</p>
89695  * <p>Windows can also be linked to a {@link Ext.ZIndexManager} or managed by the {@link Ext.WindowManager} to provide
89696  * grouping, activation, to front, to back and other application-specific behavior.</p>
89697  * <p>By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element
89698  * specify {@link Ext.Component#renderTo renderTo}.</p>
89699  * <p><b>As with all {@link Ext.container.Container Container}s, it is important to consider how you want the Window
89700  * to size and arrange any child Components. Choose an appropriate {@link #layout} configuration which lays out
89701  * child Components in the required manner.</b></p>
89702  * {@img Ext.window.Window/Ext.window.Window.png Window component}
89703  * Example:<code><pre>
89704 Ext.create('Ext.window.Window', {
89705     title: 'Hello',
89706     height: 200,
89707     width: 400,
89708     layout: 'fit',
89709     items: {  // Let's put an empty grid in just to illustrate fit layout
89710         xtype: 'grid',
89711         border: false,
89712         columns: [{header: 'World'}],                 // One header just for show. There's no data,
89713         store: Ext.create('Ext.data.ArrayStore', {}) // A dummy empty data store
89714     }
89715 }).show();
89716 </pre></code>
89717  * @constructor
89718  * @param {Object} config The config object
89719  * @xtype window
89720  */
89721 Ext.define('Ext.window.Window', {
89722     extend: 'Ext.panel.Panel',
89723
89724     alternateClassName: 'Ext.Window',
89725
89726     requires: ['Ext.util.ComponentDragger', 'Ext.util.Region', 'Ext.EventManager'],
89727
89728     alias: 'widget.window',
89729
89730     /**
89731      * @cfg {Number} x
89732      * The X position of the left edge of the window on initial showing. Defaults to centering the Window within
89733      * the width of the Window's container {@link Ext.core.Element Element) (The Element that the Window is rendered to).
89734      */
89735     /**
89736      * @cfg {Number} y
89737      * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within
89738      * the height of the Window's container {@link Ext.core.Element Element) (The Element that the Window is rendered to).
89739      */
89740     /**
89741      * @cfg {Boolean} modal
89742      * True to make the window modal and mask everything behind it when displayed, false to display it without
89743      * restricting access to other UI elements (defaults to false).
89744      */
89745     /**
89746      * @cfg {String/Element} animateTarget
89747      * Id or element from which the window should animate while opening (defaults to null with no animation).
89748      */
89749     /**
89750      * @cfg {String/Number/Component} defaultFocus
89751      * <p>Specifies a Component to receive focus when this Window is focused.</p>
89752      * <p>This may be one of:</p><div class="mdetail-params"><ul>
89753      * <li>The index of a footer Button.</li>
89754      * <li>The id or {@link Ext.AbstractComponent#itemId} of a descendant Component.</li>
89755      * <li>A Component.</li>
89756      * </ul></div>
89757      */
89758     /**
89759      * @cfg {Function} onEsc
89760      * Allows override of the built-in processing for the escape key. Default action
89761      * is to close the Window (performing whatever action is specified in {@link #closeAction}.
89762      * To prevent the Window closing when the escape key is pressed, specify this as
89763      * Ext.emptyFn (See {@link Ext#emptyFn Ext.emptyFn}).
89764      */
89765     /**
89766      * @cfg {Boolean} collapsed
89767      * True to render the window collapsed, false to render it expanded (defaults to false). Note that if
89768      * {@link #expandOnShow} is true (the default) it will override the <code>collapsed</code> config and the window
89769      * will always be expanded when shown.
89770      */
89771     /**
89772      * @cfg {Boolean} maximized
89773      * True to initially display the window in a maximized state. (Defaults to false).
89774      */
89775
89776     /**
89777     * @cfg {String} baseCls
89778     * The base CSS class to apply to this panel's element (defaults to 'x-window').
89779     */
89780     baseCls: Ext.baseCSSPrefix + 'window',
89781
89782     /**
89783      * @cfg {Mixed} resizable
89784      * <p>Specify as <code>true</code> to allow user resizing at each edge and corner of the window, false to disable
89785      * resizing (defaults to true).</p>
89786      * <p>This may also be specified as a config object to </p>
89787      */
89788     resizable: true,
89789
89790     /**
89791      * @cfg {Boolean} draggable
89792      * <p>True to allow the window to be dragged by the header bar, false to disable dragging (defaults to true).  Note
89793      * that by default the window will be centered in the viewport, so if dragging is disabled the window may need
89794      * to be positioned programmatically after render (e.g., myWindow.setPosition(100, 100);).<p>
89795      */
89796     draggable: true,
89797
89798     /**
89799      * @cfg {Boolean} constrain
89800      * True to constrain the window within its containing element, false to allow it to fall outside of its
89801      * containing element. By default the window will be rendered to document.body.  To render and constrain the
89802      * window within another element specify {@link #renderTo}.
89803      * (defaults to false).  Optionally the header only can be constrained using {@link #constrainHeader}.
89804      */
89805     constrain: false,
89806
89807     /**
89808      * @cfg {Boolean} constrainHeader
89809      * True to constrain the window header within its containing element (allowing the window body to fall outside
89810      * of its containing element) or false to allow the header to fall outside its containing element (defaults to
89811      * false). Optionally the entire window can be constrained using {@link #constrain}.
89812      */
89813     constrainHeader: false,
89814
89815     /**
89816      * @cfg {Boolean} plain
89817      * True to render the window body with a transparent background so that it will blend into the framing
89818      * elements, false to add a lighter background color to visually highlight the body element and separate it
89819      * more distinctly from the surrounding frame (defaults to false).
89820      */
89821     plain: false,
89822
89823     /**
89824      * @cfg {Boolean} minimizable
89825      * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button
89826      * and disallow minimizing the window (defaults to false).  Note that this button provides no implementation --
89827      * the behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a
89828      * custom minimize behavior implemented for this option to be useful.
89829      */
89830     minimizable: false,
89831
89832     /**
89833      * @cfg {Boolean} maximizable
89834      * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button
89835      * and disallow maximizing the window (defaults to false).  Note that when a window is maximized, the tool button
89836      * will automatically change to a 'restore' button with the appropriate behavior already built-in that will
89837      * restore the window to its previous size.
89838      */
89839     maximizable: false,
89840
89841     // inherit docs
89842     minHeight: 100,
89843
89844     // inherit docs
89845     minWidth: 200,
89846
89847     /**
89848      * @cfg {Boolean} expandOnShow
89849      * True to always expand the window when it is displayed, false to keep it in its current state (which may be
89850      * {@link #collapsed}) when displayed (defaults to true).
89851      */
89852     expandOnShow: true,
89853
89854     // inherited docs, same default
89855     collapsible: false,
89856
89857     /**
89858      * @cfg {Boolean} closable
89859      * <p>True to display the 'close' tool button and allow the user to close the window, false to
89860      * hide the button and disallow closing the window (defaults to <code>true</code>).</p>
89861      * <p>By default, when close is requested by either clicking the close button in the header
89862      * or pressing ESC when the Window has focus, the {@link #close} method will be called. This
89863      * will <i>{@link Ext.Component#destroy destroy}</i> the Window and its content meaning that
89864      * it may not be reused.</p>
89865      * <p>To make closing a Window <i>hide</i> the Window so that it may be reused, set
89866      * {@link #closeAction} to 'hide'.</p>
89867      */
89868     closable: true,
89869
89870     /**
89871      * @cfg {Boolean} hidden
89872      * Render this Window hidden (default is <code>true</code>). If <code>true</code>, the
89873      * {@link #hide} method will be called internally.
89874      */
89875     hidden: true,
89876
89877     // Inherit docs from Component. Windows render to the body on first show.
89878     autoRender: true,
89879
89880     // Inherit docs from Component. Windows hide using visibility.
89881     hideMode: 'visibility',
89882
89883     /** @cfg {Boolean} floating @hide Windows are always floating*/
89884     floating: true,
89885
89886     ariaRole: 'alertdialog',
89887     
89888     itemCls: 'x-window-item',
89889
89890     overlapHeader: true,
89891     
89892     ignoreHeaderBorderManagement: true,
89893
89894     // private
89895     initComponent: function() {
89896         var me = this;
89897         me.callParent();
89898         me.addEvents(
89899             /**
89900              * @event activate
89901              * Fires after the window has been visually activated via {@link #setActive}.
89902              * @param {Ext.window.Window} this
89903              */
89904             /**
89905              * @event deactivate
89906              * Fires after the window has been visually deactivated via {@link #setActive}.
89907              * @param {Ext.window.Window} this
89908              */
89909             /**
89910              * @event resize
89911              * Fires after the window has been resized.
89912              * @param {Ext.window.Window} this
89913              * @param {Number} width The window's new width
89914              * @param {Number} height The window's new height
89915              */
89916             'resize',
89917             /**
89918              * @event maximize
89919              * Fires after the window has been maximized.
89920              * @param {Ext.window.Window} this
89921              */
89922             'maximize',
89923             /**
89924              * @event minimize
89925              * Fires after the window has been minimized.
89926              * @param {Ext.window.Window} this
89927              */
89928             'minimize',
89929             /**
89930              * @event restore
89931              * Fires after the window has been restored to its original size after being maximized.
89932              * @param {Ext.window.Window} this
89933              */
89934             'restore'
89935         );
89936
89937         if (me.plain) {
89938             me.addClsWithUI('plain');
89939         }
89940
89941         if (me.modal) {
89942             me.ariaRole = 'dialog';
89943         }
89944     },
89945
89946     // State Management
89947     // private
89948
89949     initStateEvents: function(){
89950         var events = this.stateEvents;
89951         // push on stateEvents if they don't exist
89952         Ext.each(['maximize', 'restore', 'resize', 'dragend'], function(event){
89953             if (Ext.Array.indexOf(events, event)) {
89954                 events.push(event);
89955             }
89956         });
89957         this.callParent();
89958     },
89959
89960     getState: function() {
89961         var me = this,
89962             state = me.callParent() || {},
89963             maximized = !!me.maximized;
89964
89965         state.maximized = maximized;
89966         Ext.apply(state, {
89967             size: maximized ? me.restoreSize : me.getSize(),
89968             pos: maximized ? me.restorePos : me.getPosition()
89969         });
89970         return state;
89971     },
89972
89973     applyState: function(state){
89974         var me = this;
89975
89976         if (state) {
89977             me.maximized = state.maximized;
89978             if (me.maximized) {
89979                 me.hasSavedRestore = true;
89980                 me.restoreSize = state.size;
89981                 me.restorePos = state.pos;
89982             } else {
89983                 Ext.apply(me, {
89984                     width: state.size.width,
89985                     height: state.size.height,
89986                     x: state.pos[0],
89987                     y: state.pos[1]
89988                 });
89989             }
89990         }
89991     },
89992
89993     // private
89994     onMouseDown: function () {
89995         if (this.floating) {
89996             this.toFront();
89997         }
89998     },
89999
90000     // private
90001     onRender: function(ct, position) {
90002         var me = this;
90003         me.callParent(arguments);
90004         me.focusEl = me.el;
90005
90006         // Double clicking a header will toggleMaximize
90007         if (me.maximizable) {
90008             me.header.on({
90009                 dblclick: {
90010                     fn: me.toggleMaximize,
90011                     element: 'el',
90012                     scope: me
90013                 }
90014             });
90015         }
90016     },
90017
90018     // private
90019     afterRender: function() {
90020         var me = this,
90021             hidden = me.hidden,
90022             keyMap;
90023
90024         me.hidden = false;
90025         // Component's afterRender sizes and positions the Component
90026         me.callParent();
90027         me.hidden = hidden;
90028
90029         // Create the proxy after the size has been applied in Component.afterRender
90030         me.proxy = me.getProxy();
90031
90032         // clickToRaise
90033         me.mon(me.el, 'mousedown', me.onMouseDown, me);
90034
90035         // Initialize
90036         if (me.maximized) {
90037             me.maximized = false;
90038             me.maximize();
90039         }
90040
90041         if (me.closable) {
90042             keyMap = me.getKeyMap();
90043             keyMap.on(27, me.onEsc, me);
90044
90045             //if (hidden) { ? would be consistent w/before/afterShow...
90046                 keyMap.disable();
90047             //}
90048         }
90049
90050         if (!hidden) {
90051             me.syncMonitorWindowResize();
90052             me.doConstrain();
90053         }
90054     },
90055
90056     /**
90057      * @private
90058      * @override
90059      * Override Component.initDraggable.
90060      * Window uses the header element as the delegate.
90061      */
90062     initDraggable: function() {
90063         var me = this,
90064             ddConfig;
90065
90066         if (!me.header) {
90067             me.updateHeader(true);
90068         }
90069         
90070         /*
90071          * Check the header here again. If for whatever reason it wasn't created in
90072          * updateHeader (preventHeader) then we'll just ignore the rest since the
90073          * header acts as the drag handle.
90074          */
90075         if (me.header) {
90076             ddConfig = Ext.applyIf({
90077                 el: me.el,
90078                 delegate: '#' + me.header.id
90079             }, me.draggable);
90080
90081             // Add extra configs if Window is specified to be constrained
90082             if (me.constrain || me.constrainHeader) {
90083                 ddConfig.constrain = me.constrain;
90084                 ddConfig.constrainDelegate = me.constrainHeader;
90085                 ddConfig.constrainTo = me.constrainTo || me.container;
90086             }
90087
90088             /**
90089              * <p>If this Window is configured {@link #draggable}, this property will contain
90090              * an instance of {@link Ext.util.ComponentDragger} (A subclass of {@link Ext.dd.DragTracker DragTracker})
90091              * which handles dragging the Window's DOM Element, and constraining according to the {@link #constrain}
90092              * and {@link #constrainHeader} .</p>
90093              * <p>This has implementations of <code>onBeforeStart</code>, <code>onDrag</code> and <code>onEnd</code>
90094              * which perform the dragging action. If extra logic is needed at these points, use
90095              * {@link Ext.Function#createInterceptor createInterceptor} or {@link Ext.Function#createSequence createSequence} to
90096              * augment the existing implementations.</p>
90097              * @type Ext.util.ComponentDragger
90098              * @property dd
90099              */
90100             me.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
90101             me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']);
90102         }
90103     },
90104
90105     // private
90106     onEsc: function(k, e) {
90107         e.stopEvent();
90108         this[this.closeAction]();
90109     },
90110
90111     // private
90112     beforeDestroy: function() {
90113         var me = this;
90114         if (me.rendered) {
90115             delete this.animateTarget;
90116             me.hide();
90117             Ext.destroy(
90118                 me.keyMap
90119             );
90120         }
90121         me.callParent();
90122     },
90123
90124     /**
90125      * @private
90126      * @override
90127      * Contribute class-specific tools to the header.
90128      * Called by Panel's initTools.
90129      */
90130     addTools: function() {
90131         var me = this;
90132
90133         // Call Panel's initTools
90134         me.callParent();
90135
90136         if (me.minimizable) {
90137             me.addTool({
90138                 type: 'minimize',
90139                 handler: Ext.Function.bind(me.minimize, me, [])
90140             });
90141         }
90142         if (me.maximizable) {
90143             me.addTool({
90144                 type: 'maximize',
90145                 handler: Ext.Function.bind(me.maximize, me, [])
90146             });
90147             me.addTool({
90148                 type: 'restore',
90149                 handler: Ext.Function.bind(me.restore, me, []),
90150                 hidden: true
90151             });
90152         }
90153     },
90154
90155     /**
90156      * Gets the configured default focus item.  If a {@link #defaultFocus} is set, it will receive focus, otherwise the
90157      * Container itself will receive focus.
90158      */
90159     getFocusEl: function() {
90160         var me = this,
90161             f = me.focusEl,
90162             defaultComp = me.defaultButton || me.defaultFocus,
90163             t = typeof db,
90164             el,
90165             ct;
90166
90167         if (Ext.isDefined(defaultComp)) {
90168             if (Ext.isNumber(defaultComp)) {
90169                 f = me.query('button')[defaultComp];
90170             } else if (Ext.isString(defaultComp)) {
90171                 f = me.down('#' + defaultComp);
90172             } else {
90173                 f = defaultComp;
90174             }
90175         }
90176         return f || me.focusEl;
90177     },
90178
90179     // private
90180     beforeShow: function() {
90181         this.callParent();
90182
90183         if (this.expandOnShow) {
90184             this.expand(false);
90185         }
90186     },
90187
90188     // private
90189     afterShow: function(animateTarget) {
90190         var me = this;
90191
90192         // Perform superclass's afterShow tasks
90193         // Which might include animating a proxy from an animTarget
90194         me.callParent(arguments);
90195
90196         if (me.maximized) {
90197             me.fitContainer();
90198         }
90199
90200         me.syncMonitorWindowResize();
90201         me.doConstrain();
90202
90203         if (me.keyMap) {
90204             me.keyMap.enable();
90205         }
90206     },
90207
90208     // private
90209     doClose: function() {
90210         var me = this;
90211
90212         // immediate close
90213         if (me.hidden) {
90214             me.fireEvent('close', me);
90215             me[me.closeAction]();
90216         } else {
90217             // close after hiding
90218             me.hide(me.animTarget, me.doClose, me);
90219         }
90220     },
90221
90222     // private
90223     afterHide: function() {
90224         var me = this;
90225
90226         // No longer subscribe to resizing now that we're hidden
90227         me.syncMonitorWindowResize();
90228
90229         // Turn off keyboard handling once window is hidden
90230         if (me.keyMap) {
90231             me.keyMap.disable();
90232         }
90233
90234         // Perform superclass's afterHide tasks.
90235         me.callParent(arguments);
90236     },
90237
90238     // private
90239     onWindowResize: function() {
90240         if (this.maximized) {
90241             this.fitContainer();
90242         }
90243         this.doConstrain();
90244     },
90245
90246     /**
90247      * Placeholder method for minimizing the window.  By default, this method simply fires the {@link #minimize} event
90248      * since the behavior of minimizing a window is application-specific.  To implement custom minimize behavior,
90249      * either the minimize event can be handled or this method can be overridden.
90250      * @return {Ext.window.Window} this
90251      */
90252     minimize: function() {
90253         this.fireEvent('minimize', this);
90254         return this;
90255     },
90256
90257     afterCollapse: function() {
90258         var me = this;
90259
90260         if (me.maximizable) {
90261             me.tools.maximize.hide();
90262             me.tools.restore.hide();
90263         }
90264         if (me.resizer) {
90265             me.resizer.disable();
90266         }
90267         me.callParent(arguments);
90268     },
90269
90270     afterExpand: function() {
90271         var me = this;
90272
90273         if (me.maximized) {
90274             me.tools.restore.show();
90275         } else if (me.maximizable) {
90276             me.tools.maximize.show();
90277         }
90278         if (me.resizer) {
90279             me.resizer.enable();
90280         }
90281         me.callParent(arguments);
90282     },
90283
90284     /**
90285      * Fits the window within its current container and automatically replaces
90286      * the {@link #maximizable 'maximize' tool button} with the 'restore' tool button.
90287      * Also see {@link #toggleMaximize}.
90288      * @return {Ext.window.Window} this
90289      */
90290     maximize: function() {
90291         var me = this;
90292
90293         if (!me.maximized) {
90294             me.expand(false);
90295             if (!me.hasSavedRestore) {
90296                 me.restoreSize = me.getSize();
90297                 me.restorePos = me.getPosition(true);
90298             }
90299             if (me.maximizable) {
90300                 me.tools.maximize.hide();
90301                 me.tools.restore.show();
90302             }
90303             me.maximized = true;
90304             me.el.disableShadow();
90305
90306             if (me.dd) {
90307                 me.dd.disable();
90308             }
90309             if (me.collapseTool) {
90310                 me.collapseTool.hide();
90311             }
90312             me.el.addCls(Ext.baseCSSPrefix + 'window-maximized');
90313             me.container.addCls(Ext.baseCSSPrefix + 'window-maximized-ct');
90314
90315             me.syncMonitorWindowResize();
90316             me.setPosition(0, 0);
90317             me.fitContainer();
90318             me.fireEvent('maximize', me);
90319         }
90320         return me;
90321     },
90322
90323     /**
90324      * Restores a {@link #maximizable maximized}  window back to its original
90325      * size and position prior to being maximized and also replaces
90326      * the 'restore' tool button with the 'maximize' tool button.
90327      * Also see {@link #toggleMaximize}.
90328      * @return {Ext.window.Window} this
90329      */
90330     restore: function() {
90331         var me = this,
90332             tools = me.tools;
90333
90334         if (me.maximized) {
90335             delete me.hasSavedRestore;
90336             me.removeCls(Ext.baseCSSPrefix + 'window-maximized');
90337
90338             // Toggle tool visibility
90339             if (tools.restore) {
90340                 tools.restore.hide();
90341             }
90342             if (tools.maximize) {
90343                 tools.maximize.show();
90344             }
90345             if (me.collapseTool) {
90346                 me.collapseTool.show();
90347             }
90348
90349             // Restore the position/sizing
90350             me.setPosition(me.restorePos);
90351             me.setSize(me.restoreSize);
90352
90353             // Unset old position/sizing
90354             delete me.restorePos;
90355             delete me.restoreSize;
90356
90357             me.maximized = false;
90358
90359             me.el.enableShadow(true);
90360
90361             // Allow users to drag and drop again
90362             if (me.dd) {
90363                 me.dd.enable();
90364             }
90365
90366             me.container.removeCls(Ext.baseCSSPrefix + 'window-maximized-ct');
90367
90368             me.syncMonitorWindowResize();
90369             me.doConstrain();
90370             me.fireEvent('restore', me);
90371         }
90372         return me;
90373     },
90374
90375     /**
90376      * Synchronizes the presence of our listener for window resize events. This method
90377      * should be called whenever this status might change.
90378      * @private
90379      */
90380     syncMonitorWindowResize: function () {
90381         var me = this,
90382             currentlyMonitoring = me._monitoringResize,
90383             // all the states where we should be listening to window resize:
90384             yes = me.monitorResize || me.constrain || me.constrainHeader || me.maximized,
90385             // all the states where we veto this:
90386             veto = me.hidden || me.destroying || me.isDestroyed;
90387
90388         if (yes && !veto) {
90389             // we should be listening...
90390             if (!currentlyMonitoring) {
90391                 // but we aren't, so set it up
90392                 Ext.EventManager.onWindowResize(me.onWindowResize, me);
90393                 me._monitoringResize = true;
90394             }
90395         } else if (currentlyMonitoring) {
90396             // we should not be listening, but we are, so tear it down
90397             Ext.EventManager.removeResizeListener(me.onWindowResize, me);
90398             me._monitoringResize = false;
90399         }
90400     },
90401
90402     /**
90403      * A shortcut method for toggling between {@link #maximize} and {@link #restore} based on the current maximized
90404      * state of the window.
90405      * @return {Ext.window.Window} this
90406      */
90407     toggleMaximize: function() {
90408         return this[this.maximized ? 'restore': 'maximize']();
90409     }
90410
90411     /**
90412      * @cfg {Boolean} autoWidth @hide
90413      * Absolute positioned element and therefore cannot support autoWidth.
90414      * A width is a required configuration.
90415      **/
90416 });
90417 /**
90418  * @class Ext.form.field.Base
90419  * @extends Ext.Component
90420
90421 Base class for form fields that provides default event handling, rendering, and other common functionality
90422 needed by all form field types. Utilizes the {@link Ext.form.field.Field} mixin for value handling and validation,
90423 and the {@link Ext.form.Labelable} mixin to provide label and error message display.
90424
90425 In most cases you will want to use a subclass, such as {@link Ext.form.field.Text} or {@link Ext.form.field.Checkbox},
90426 rather than creating instances of this class directly. However if you are implementing a custom form field,
90427 using this as the parent class is recommended.
90428
90429 __Values and Conversions__
90430
90431 Because BaseField implements the Field mixin, it has a main value that can be initialized with the
90432 {@link #value} config and manipulated via the {@link #getValue} and {@link #setValue} methods. This main
90433 value can be one of many data types appropriate to the current field, for instance a {@link Ext.form.field.Date Date}
90434 field would use a JavaScript Date object as its value type. However, because the field is rendered as a HTML
90435 input, this value data type can not always be directly used in the rendered field.
90436
90437 Therefore BaseField introduces the concept of a "raw value". This is the value of the rendered HTML input field,
90438 and is normally a String. The {@link #getRawValue} and {@link #setRawValue} methods can be used to directly
90439 work with the raw value, though it is recommended to use getValue and setValue in most cases.
90440
90441 Conversion back and forth between the main value and the raw value is handled by the {@link #valueToRaw} and
90442 {@link #rawToValue} methods. If you are implementing a subclass that uses a non-String value data type, you
90443 should override these methods to handle the conversion.
90444
90445 __Rendering__
90446
90447 The content of the field body is defined by the {@link #fieldSubTpl} XTemplate, with its argument data
90448 created by the {@link #getSubTplData} method. Override this template and/or method to create custom
90449 field renderings.
90450 {@img Ext.form.BaseField/Ext.form.BaseField.png Ext.form.BaseField BaseField component}
90451 __Example usage:__
90452
90453     // A simple subclass of BaseField that creates a HTML5 search field. Redirects to the
90454     // searchUrl when the Enter key is pressed.
90455     Ext.define('Ext.form.SearchField', {
90456         extend: 'Ext.form.field.Base',
90457         alias: 'widget.searchfield',
90458     
90459         inputType: 'search',
90460     
90461         // Config defining the search URL
90462         searchUrl: 'http://www.google.com/search?q={0}',
90463     
90464         // Add specialkey listener
90465         initComponent: function() {
90466             this.callParent();
90467             this.on('specialkey', this.checkEnterKey, this);
90468         },
90469     
90470         // Handle enter key presses, execute the search if the field has a value
90471         checkEnterKey: function(field, e) {
90472             var value = this.getValue();
90473             if (e.getKey() === e.ENTER && !Ext.isEmpty(value)) {
90474                 location.href = Ext.String.format(this.searchUrl, value);
90475             }
90476         }
90477     });
90478
90479     Ext.create('Ext.form.Panel', {
90480         title: 'BaseField Example',
90481         bodyPadding: 5,
90482         width: 250,
90483                 
90484         // Fields will be arranged vertically, stretched to full width
90485         layout: 'anchor',
90486         defaults: {
90487             anchor: '100%'
90488         },
90489         items: [{
90490             xtype: 'searchfield',
90491             fieldLabel: 'Search',
90492             name: 'query'
90493         }]
90494         renderTo: Ext.getBody()
90495     });
90496
90497  * @constructor
90498  * Creates a new Field
90499  * @param {Object} config Configuration options
90500  *
90501  * @xtype field
90502  * @markdown
90503  * @docauthor Jason Johnston <jason@sencha.com>
90504  */
90505 Ext.define('Ext.form.field.Base', {
90506     extend: 'Ext.Component',
90507     mixins: {
90508         labelable: 'Ext.form.Labelable',
90509         field: 'Ext.form.field.Field'
90510     },
90511     alias: 'widget.field',
90512     alternateClassName: ['Ext.form.Field', 'Ext.form.BaseField'],
90513     requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.field.Field'],
90514
90515     fieldSubTpl: [
90516         '<input id="{id}" type="{type}" ',
90517         '<tpl if="name">name="{name}" </tpl>',
90518         '<tpl if="size">size="{size}" </tpl>',
90519         '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
90520         'class="{fieldCls} {typeCls}" autocomplete="off" />',
90521         {
90522             compiled: true,
90523             disableFormats: true
90524         }
90525     ],
90526
90527     /**
90528      * @cfg {String} name The name of the field (defaults to undefined). This is used as the parameter
90529      * name when including the field value in a {@link Ext.form.Basic#submit form submit()}. If no name is
90530      * configured, it falls back to the {@link #inputId}. To prevent the field from being included in the
90531      * form submit, set {@link #submitValue} to <tt>false</tt>.
90532      */
90533
90534     /**
90535      * @cfg {String} inputType
90536      * <p>The type attribute for input fields -- e.g. radio, text, password, file (defaults to <tt>'text'</tt>).
90537      * The extended types supported by HTML5 inputs (url, email, etc.) may also be used, though using them
90538      * will cause older browsers to fall back to 'text'.</p>
90539      * <p>The type 'password' must be used to render that field type currently -- there is no separate Ext
90540      * component for that. You can use {@link Ext.form.field.File} which creates a custom-rendered file upload
90541      * field, but if you want a plain unstyled file input you can use a BaseField with inputType:'file'.</p>
90542      */
90543     inputType: 'text',
90544
90545     /**
90546      * @cfg {Number} tabIndex The tabIndex for this field. Note this only applies to fields that are rendered,
90547      * not those which are built via applyTo (defaults to undefined).
90548      */
90549
90550     /**
90551      * @cfg {String} invalidText The error text to use when marking a field invalid and no message is provided
90552      * (defaults to 'The value in this field is invalid')
90553      */
90554     invalidText : 'The value in this field is invalid',
90555
90556     /**
90557      * @cfg {String} fieldCls The default CSS class for the field input (defaults to 'x-form-field')
90558      */
90559     fieldCls : Ext.baseCSSPrefix + 'form-field',
90560
90561     /**
90562      * @cfg {String} fieldStyle Optional CSS style(s) to be applied to the {@link #inputEl field input element}.
90563      * Should be a valid argument to {@link Ext.core.Element#applyStyles}. Defaults to undefined. See also the
90564      * {@link #setFieldStyle} method for changing the style after initialization.
90565      */
90566
90567     /**
90568      * @cfg {String} focusCls The CSS class to use when the field receives focus (defaults to 'x-form-focus')
90569      */
90570     focusCls : Ext.baseCSSPrefix + 'form-focus',
90571
90572     /**
90573      * @cfg {String} dirtyCls The CSS class to use when the field value {@link #isDirty is dirty}.
90574      */
90575     dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
90576
90577     /**
90578      * @cfg {Array} checkChangeEvents
90579      * <p>A list of event names that will be listened for on the field's {@link #inputEl input element}, which
90580      * will cause the field's value to be checked for changes. If a change is detected, the
90581      * {@link #change change event} will be fired, followed by validation if the {@link #validateOnChange}
90582      * option is enabled.</p>
90583      * <p>Defaults to <tt>['change', 'propertychange']</tt> in Internet Explorer, and <tt>['change', 'input',
90584      * 'textInput', 'keyup', 'dragdrop']</tt> in other browsers. This catches all the ways that field values
90585      * can be changed in most supported browsers; the only known exceptions at the time of writing are:</p>
90586      * <ul>
90587      * <li>Safari 3.2 and older: cut/paste in textareas via the context menu, and dragging text into textareas</li>
90588      * <li>Opera 10 and 11: dragging text into text fields and textareas, and cut via the context menu in text
90589      * fields and textareas</li>
90590      * <li>Opera 9: Same as Opera 10 and 11, plus paste from context menu in text fields and textareas</li>
90591      * </ul>
90592      * <p>If you need to guarantee on-the-fly change notifications including these edge cases, you can call the
90593      * {@link #checkChange} method on a repeating interval, e.g. using {@link Ext.TaskManager}, or if the field is
90594      * within a {@link Ext.form.Panel}, you can use the FormPanel's {@link Ext.form.Panel#pollForChanges}
90595      * configuration to set up such a task automatically.</p>
90596      */
90597     checkChangeEvents: Ext.isIE && (!document.documentMode || document.documentMode < 9) ?
90598                         ['change', 'propertychange'] :
90599                         ['change', 'input', 'textInput', 'keyup', 'dragdrop'],
90600
90601     /**
90602      * @cfg {Number} checkChangeBuffer
90603      * Defines a timeout in milliseconds for buffering {@link #checkChangeEvents} that fire in rapid succession.
90604      * Defaults to 50 milliseconds.
90605      */
90606     checkChangeBuffer: 50,
90607
90608     componentLayout: 'field',
90609
90610     /**
90611      * @cfg {Boolean} readOnly <tt>true</tt> to mark the field as readOnly in HTML
90612      * (defaults to <tt>false</tt>).
90613      * <br><p><b>Note</b>: this only sets the element's readOnly DOM attribute.
90614      * Setting <code>readOnly=true</code>, for example, will not disable triggering a
90615      * ComboBox or Date; it gives you the option of forcing the user to choose
90616      * via the trigger without typing in the text box. To hide the trigger use
90617      * <code>{@link Ext.form.field.Trigger#hideTrigger hideTrigger}</code>.</p>
90618      */
90619     readOnly : false,
90620
90621     /**
90622      * @cfg {String} readOnlyCls The CSS class applied to the component's main element when it is {@link #readOnly}.
90623      */
90624     readOnlyCls: Ext.baseCSSPrefix + 'form-readonly',
90625
90626     /**
90627      * @cfg {String} inputId
90628      * The id that will be given to the generated input DOM element. Defaults to an automatically generated id.
90629      * If you configure this manually, you must make sure it is unique in the document.
90630      */
90631
90632     /**
90633      * @cfg {Boolean} validateOnBlur
90634      * Whether the field should validate when it loses focus (defaults to <tt>true</tt>). This will cause fields
90635      * to be validated as the user steps through the fields in the form regardless of whether they are making
90636      * changes to those fields along the way. See also {@link #validateOnChange}.
90637      */
90638     validateOnBlur: true,
90639
90640     // private
90641     hasFocus : false,
90642     
90643     baseCls: Ext.baseCSSPrefix + 'field',
90644     
90645     maskOnDisable: false,
90646
90647     // private
90648     initComponent : function() {
90649         var me = this;
90650
90651         me.callParent();
90652
90653         me.subTplData = me.subTplData || {};
90654
90655         me.addEvents(
90656             /**
90657              * @event focus
90658              * Fires when this field receives input focus.
90659              * @param {Ext.form.field.Base} this
90660              */
90661             'focus',
90662             /**
90663              * @event blur
90664              * Fires when this field loses input focus.
90665              * @param {Ext.form.field.Base} this
90666              */
90667             'blur',
90668             /**
90669              * @event specialkey
90670              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.
90671              * To handle other keys see {@link Ext.panel.Panel#keys} or {@link Ext.util.KeyMap}.
90672              * You can check {@link Ext.EventObject#getKey} to determine which key was pressed.
90673              * For example: <pre><code>
90674 var form = new Ext.form.Panel({
90675     ...
90676     items: [{
90677             fieldLabel: 'Field 1',
90678             name: 'field1',
90679             allowBlank: false
90680         },{
90681             fieldLabel: 'Field 2',
90682             name: 'field2',
90683             listeners: {
90684                 specialkey: function(field, e){
90685                     // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
90686                     // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
90687                     if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
90688                         var form = field.up('form').getForm();
90689                         form.submit();
90690                     }
90691                 }
90692             }
90693         }
90694     ],
90695     ...
90696 });
90697              * </code></pre>
90698              * @param {Ext.form.field.Base} this
90699              * @param {Ext.EventObject} e The event object
90700              */
90701             'specialkey'
90702         );
90703
90704         // Init mixins
90705         me.initLabelable();
90706         me.initField();
90707
90708         // Default name to inputId
90709         if (!me.name) {
90710             me.name = me.getInputId();
90711         }
90712     },
90713
90714     /**
90715      * Returns the input id for this field. If none was specified via the {@link #inputId} config,
90716      * then an id will be automatically generated.
90717      */
90718     getInputId: function() {
90719         return this.inputId || (this.inputId = Ext.id());
90720     },
90721
90722     /**
90723      * @protected Creates and returns the data object to be used when rendering the {@link #fieldSubTpl}.
90724      * @return {Object} The template data
90725      */
90726     getSubTplData: function() {
90727         var me = this,
90728             type = me.inputType,
90729             inputId = me.getInputId();
90730
90731         return Ext.applyIf(me.subTplData, {
90732             id: inputId,
90733             name: me.name || inputId,
90734             type: type,
90735             size: me.size || 20,
90736             cls: me.cls,
90737             fieldCls: me.fieldCls,
90738             tabIdx: me.tabIndex,
90739             typeCls: Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
90740         });
90741     },
90742
90743     /**
90744      * @protected
90745      * Gets the markup to be inserted into the outer template's bodyEl. For fields this is the
90746      * actual input element.
90747      */
90748     getSubTplMarkup: function() {
90749         return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
90750     },
90751
90752     initRenderTpl: function() {
90753         var me = this;
90754         if (!me.hasOwnProperty('renderTpl')) {
90755             me.renderTpl = me.getTpl('labelableRenderTpl');
90756         }
90757         return me.callParent();
90758     },
90759
90760     initRenderData: function() {
90761         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
90762     },
90763
90764     /**
90765      * Set the {@link #fieldStyle CSS style} of the {@link #inputEl field input element}.
90766      * @param {String/Object/Function} style The style(s) to apply. Should be a valid argument to
90767      * {@link Ext.core.Element#applyStyles}.
90768      */
90769     setFieldStyle: function(style) {
90770         var me = this,
90771             inputEl = me.inputEl;
90772         if (inputEl) {
90773             inputEl.applyStyles(style);
90774         }
90775         me.fieldStyle = style;
90776     },
90777
90778     // private
90779     onRender : function() {
90780         var me = this,
90781             fieldStyle = me.fieldStyle,
90782             renderSelectors = me.renderSelectors;
90783
90784         Ext.applyIf(renderSelectors, me.getLabelableSelectors());
90785
90786         Ext.applyIf(renderSelectors, {
90787             /**
90788              * @property inputEl
90789              * @type Ext.core.Element
90790              * The input Element for this Field. Only available after the field has been rendered.
90791              */
90792             inputEl: '.' + me.fieldCls
90793         });
90794
90795         me.callParent(arguments);
90796
90797         // Make the stored rawValue get set as the input element's value
90798         me.setRawValue(me.rawValue);
90799
90800         if (me.readOnly) {
90801             me.setReadOnly(true);
90802         }
90803         if (me.disabled) {
90804             me.disable();
90805         }
90806         if (fieldStyle) {
90807             me.setFieldStyle(fieldStyle);
90808         }
90809
90810         me.renderActiveError();
90811     },
90812
90813     initAria: function() {
90814         var me = this;
90815         me.callParent();
90816
90817         // Associate the field to the error message element
90818         me.getActionEl().dom.setAttribute('aria-describedby', Ext.id(me.errorEl));
90819     },
90820
90821     getFocusEl: function() {
90822         return this.inputEl;
90823     },
90824
90825     isFileUpload: function() {
90826         return this.inputType === 'file';
90827     },
90828
90829     extractFileInput: function() {
90830         var me = this,
90831             fileInput = me.isFileUpload() ? me.inputEl.dom : null,
90832             clone;
90833         if (fileInput) {
90834             clone = fileInput.cloneNode(true);
90835             fileInput.parentNode.replaceChild(clone, fileInput);
90836             me.inputEl = Ext.get(clone);
90837         }
90838         return fileInput;
90839     },
90840
90841     // private override to use getSubmitValue() as a convenience
90842     getSubmitData: function() {
90843         var me = this,
90844             data = null,
90845             val;
90846         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
90847             val = me.getSubmitValue();
90848             if (val !== null) {
90849                 data = {};
90850                 data[me.getName()] = val;
90851             }
90852         }
90853         return data;
90854     },
90855
90856     /**
90857      * <p>Returns the value that would be included in a standard form submit for this field. This will be combined
90858      * with the field's name to form a <tt>name=value</tt> pair in the {@link #getSubmitData submitted parameters}.
90859      * If an empty string is returned then just the <tt>name=</tt> will be submitted; if <tt>null</tt> is returned
90860      * then nothing will be submitted.</p>
90861      * <p>Note that the value returned will have been {@link #processRawValue processed} but may or may not have
90862      * been successfully {@link #validate validated}.</p>
90863      * @return {String} The value to be submitted, or <tt>null</tt>.
90864      */
90865     getSubmitValue: function() {
90866         return this.processRawValue(this.getRawValue());
90867     },
90868
90869     /**
90870      * Returns the raw value of the field, without performing any normalization, conversion, or validation.
90871      * To get a normalized and converted value see {@link #getValue}.
90872      * @return {String} value The raw String value of the field
90873      */
90874     getRawValue: function() {
90875         var me = this,
90876             v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
90877         me.rawValue = v;
90878         return v;
90879     },
90880
90881     /**
90882      * Sets the field's raw value directly, bypassing {@link #valueToRaw value conversion}, change detection, and
90883      * validation. To set the value with these additional inspections see {@link #setValue}.
90884      * @param {Mixed} value The value to set
90885      * @return {Mixed} value The field value that is set
90886      */
90887     setRawValue: function(value) {
90888         var me = this;
90889         value = Ext.value(value, '');
90890         me.rawValue = value;
90891
90892         // Some Field subclasses may not render an inputEl
90893         if (me.inputEl) {
90894             me.inputEl.dom.value = value;
90895         }
90896         return value;
90897     },
90898
90899     /**
90900      * <p>Converts a mixed-type value to a raw representation suitable for displaying in the field. This allows
90901      * controlling how value objects passed to {@link #setValue} are shown to the user, including localization.
90902      * For instance, for a {@link Ext.form.field.Date}, this would control how a Date object passed to {@link #setValue}
90903      * would be converted to a String for display in the field.</p>
90904      * <p>See {@link #rawToValue} for the opposite conversion.</p>
90905      * <p>The base implementation simply does a standard toString conversion, and converts
90906      * {@link Ext#isEmpty empty values} to an empty string.</p>
90907      * @param {Mixed} value The mixed-type value to convert to the raw representation.
90908      * @return {Mixed} The converted raw value.
90909      */
90910     valueToRaw: function(value) {
90911         return '' + Ext.value(value, '');
90912     },
90913
90914     /**
90915      * <p>Converts a raw input field value into a mixed-type value that is suitable for this particular field type.
90916      * This allows controlling the normalization and conversion of user-entered values into field-type-appropriate
90917      * values, e.g. a Date object for {@link Ext.form.field.Date}, and is invoked by {@link #getValue}.</p>
90918      * <p>It is up to individual implementations to decide how to handle raw values that cannot be successfully
90919      * converted to the desired object type.</p>
90920      * <p>See {@link #valueToRaw} for the opposite conversion.</p>
90921      * <p>The base implementation does no conversion, returning the raw value untouched.</p>
90922      * @param {Mixed} rawValue
90923      * @return {Mixed} The converted value.
90924      */
90925     rawToValue: function(rawValue) {
90926         return rawValue;
90927     },
90928
90929     /**
90930      * Performs any necessary manipulation of a raw field value to prepare it for {@link #rawToValue conversion}
90931      * and/or {@link #validate validation}, for instance stripping out ignored characters. In the base implementation
90932      * it does nothing; individual subclasses may override this as needed.
90933      * @param {Mixed} value The unprocessed string value
90934      * @return {Mixed} The processed string value
90935      */
90936     processRawValue: function(value) {
90937         return value;
90938     },
90939
90940     /**
90941      * Returns the current data value of the field. The type of value returned is particular to the type of the
90942      * particular field (e.g. a Date object for {@link Ext.form.field.Date}), as the result of calling {@link #rawToValue} on
90943      * the field's {@link #processRawValue processed} String value. To return the raw String value, see {@link #getRawValue}.
90944      * @return {Mixed} value The field value
90945      */
90946     getValue: function() {
90947         var me = this,
90948             val = me.rawToValue(me.processRawValue(me.getRawValue()));
90949         me.value = val;
90950         return val;
90951     },
90952
90953     /**
90954      * Sets a data value into the field and runs the change detection and validation. To set the value directly
90955      * without these inspections see {@link #setRawValue}.
90956      * @param {Mixed} value The value to set
90957      * @return {Ext.form.field.Field} this
90958      */
90959     setValue: function(value) {
90960         var me = this;
90961         me.setRawValue(me.valueToRaw(value));
90962         return me.mixins.field.setValue.call(me, value);
90963     },
90964
90965
90966     //private
90967     onDisable: function() {
90968         var me = this,
90969             inputEl = me.inputEl;
90970         me.callParent();
90971         if (inputEl) {
90972             inputEl.dom.disabled = true;
90973         }
90974     },
90975
90976     //private
90977     onEnable: function() {
90978         var me = this,
90979             inputEl = me.inputEl;
90980         me.callParent();
90981         if (inputEl) {
90982             inputEl.dom.disabled = false;
90983         }
90984     },
90985
90986     /**
90987      * Sets the read only state of this field.
90988      * @param {Boolean} readOnly Whether the field should be read only.
90989      */
90990     setReadOnly: function(readOnly) {
90991         var me = this,
90992             inputEl = me.inputEl;
90993         if (inputEl) {
90994             inputEl.dom.readOnly = readOnly;
90995             inputEl.dom.setAttribute('aria-readonly', readOnly);
90996         }
90997         me[readOnly ? 'addCls' : 'removeCls'](me.readOnlyCls);
90998         me.readOnly = readOnly;
90999     },
91000
91001     // private
91002     fireKey: function(e){
91003         if(e.isSpecialKey()){
91004             this.fireEvent('specialkey', this, Ext.create('Ext.EventObjectImpl', e));
91005         }
91006     },
91007
91008     // private
91009     initEvents : function(){
91010         var me = this,
91011             inputEl = me.inputEl,
91012             onChangeTask,
91013             onChangeEvent;
91014         if (inputEl) {
91015             me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey,  me);
91016             me.mon(inputEl, 'focus', me.onFocus, me);
91017
91018             // standardise buffer across all browsers + OS-es for consistent event order.
91019             // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
91020             me.mon(inputEl, 'blur', me.onBlur, me, me.inEditor ? {buffer:10} : null);
91021
91022             // listen for immediate value changes
91023             onChangeTask = Ext.create('Ext.util.DelayedTask', me.checkChange, me);
91024             me.onChangeEvent = onChangeEvent = function() {
91025                 onChangeTask.delay(me.checkChangeBuffer);
91026             };
91027             Ext.each(me.checkChangeEvents, function(eventName) {
91028                 if (eventName === 'propertychange') {
91029                     me.usesPropertychange = true;
91030                 }
91031                 me.mon(inputEl, eventName, onChangeEvent);
91032             }, me);
91033         }
91034         me.callParent();
91035     },
91036
91037     doComponentLayout: function() {
91038         var me = this,
91039             inputEl = me.inputEl,
91040             usesPropertychange = me.usesPropertychange,
91041             ename = 'propertychange',
91042             onChangeEvent = me.onChangeEvent;
91043
91044         // In IE if propertychange is one of the checkChangeEvents, we need to remove
91045         // the listener prior to layout and re-add it after, to prevent it from firing
91046         // needlessly for attribute and style changes applied to the inputEl.
91047         if (usesPropertychange) {
91048             me.mun(inputEl, ename, onChangeEvent);
91049         }
91050         me.callParent(arguments);
91051         if (usesPropertychange) {
91052             me.mon(inputEl, ename, onChangeEvent);
91053         }
91054     },
91055
91056     // private
91057     preFocus: Ext.emptyFn,
91058
91059     // private
91060     onFocus: function() {
91061         var me = this,
91062             focusCls = me.focusCls,
91063             inputEl = me.inputEl;
91064         me.preFocus();
91065         if (focusCls && inputEl) {
91066             inputEl.addCls(focusCls);
91067         }
91068         if (!me.hasFocus) {
91069             me.hasFocus = true;
91070             me.fireEvent('focus', me);
91071         }
91072     },
91073
91074     // private
91075     beforeBlur : Ext.emptyFn,
91076
91077     // private
91078     onBlur : function(){
91079         var me = this,
91080             focusCls = me.focusCls,
91081             inputEl = me.inputEl;
91082         me.beforeBlur();
91083         if (focusCls && inputEl) {
91084             inputEl.removeCls(focusCls);
91085         }
91086         if (me.validateOnBlur) {
91087             me.validate();
91088         }
91089         me.hasFocus = false;
91090         me.fireEvent('blur', me);
91091         me.postBlur();
91092     },
91093
91094     // private
91095     postBlur : Ext.emptyFn,
91096
91097
91098     /**
91099      * @private Called when the field's dirty state changes. Adds/removes the {@link #dirtyCls} on the main element.
91100      * @param {Boolean} isDirty
91101      */
91102     onDirtyChange: function(isDirty) {
91103         this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
91104     },
91105
91106
91107     /**
91108      * Returns whether or not the field value is currently valid by
91109      * {@link #getErrors validating} the {@link #processRawValue processed raw value}
91110      * of the field. <b>Note</b>: {@link #disabled} fields are always treated as valid.
91111      * @return {Boolean} True if the value is valid, else false
91112      */
91113     isValid : function() {
91114         var me = this;
91115         return me.disabled || me.validateValue(me.processRawValue(me.getRawValue()));
91116     },
91117
91118
91119     /**
91120      * <p>Uses {@link #getErrors} to build an array of validation errors. If any errors are found, they are passed
91121      * to {@link #markInvalid} and false is returned, otherwise true is returned.</p>
91122      * <p>Previously, subclasses were invited to provide an implementation of this to process validations - from 3.2
91123      * onwards {@link #getErrors} should be overridden instead.</p>
91124      * @param {Mixed} value The value to validate
91125      * @return {Boolean} True if all validations passed, false if one or more failed
91126      */
91127     validateValue: function(value) {
91128         var me = this,
91129             errors = me.getErrors(value),
91130             isValid = Ext.isEmpty(errors);
91131         if (!me.preventMark) {
91132             if (isValid) {
91133                 me.clearInvalid();
91134             } else {
91135                 me.markInvalid(errors);
91136             }
91137         }
91138
91139         return isValid;
91140     },
91141
91142     /**
91143      * <p>Display one or more error messages associated with this field, using {@link #msgTarget} to determine how to
91144      * display the messages and applying {@link #invalidCls} to the field's UI element.</p>
91145      * <p><b>Note</b>: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to
91146      * return <code>false</code> if the value does <i>pass</i> validation. So simply marking a Field as invalid
91147      * will not prevent submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation}
91148      * option set.</p>
91149      * @param {String/Array} errors The validation message(s) to display.
91150      */
91151     markInvalid : function(errors) {
91152         // Save the message and fire the 'invalid' event
91153         var me = this,
91154             oldMsg = me.getActiveError();
91155         me.setActiveErrors(Ext.Array.from(errors));
91156         if (oldMsg !== me.getActiveError()) {
91157             me.doComponentLayout();
91158         }
91159     },
91160
91161     /**
91162      * <p>Clear any invalid styles/messages for this field.</p>
91163      * <p><b>Note</b>: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to
91164      * return <code>true</code> if the value does not <i>pass</i> validation. So simply clearing a field's errors
91165      * will not necessarily allow submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation}
91166      * option set.</p>
91167      */
91168     clearInvalid : function() {
91169         // Clear the message and fire the 'valid' event
91170         var me = this,
91171             hadError = me.hasActiveError();
91172         me.unsetActiveError();
91173         if (hadError) {
91174             me.doComponentLayout();
91175         }
91176     },
91177
91178     /**
91179      * @private Overrides the method from the Ext.form.Labelable mixin to also add the invalidCls to the inputEl,
91180      * as that is required for proper styling in IE with nested fields (due to lack of child selector)
91181      */
91182     renderActiveError: function() {
91183         var me = this,
91184             hasError = me.hasActiveError();
91185         if (me.inputEl) {
91186             // Add/remove invalid class
91187             me.inputEl[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
91188         }
91189         me.mixins.labelable.renderActiveError.call(me);
91190     },
91191
91192
91193     getActionEl: function() {
91194         return this.inputEl || this.el;
91195     }
91196
91197 });
91198
91199 /**
91200  * @class Ext.form.field.Text
91201  * @extends Ext.form.field.Base
91202  
91203 A basic text field.  Can be used as a direct replacement for traditional text inputs,
91204 or as the base class for more sophisticated input controls (like {@link Ext.form.field.TextArea}
91205 and {@link Ext.form.field.ComboBox}). Has support for empty-field placeholder values (see {@link #emptyText}).
91206
91207 #Validation#
91208
91209 The Text field has a useful set of validations built in:
91210
91211 - {@link #allowBlank} for making the field required
91212 - {@link #minLength} for requiring a minimum value length
91213 - {@link #maxLength} for setting a maximum value length (with {@link #enforceMaxLength} to add it
91214   as the `maxlength` attribute on the input element)
91215 - {@link regex} to specify a custom regular expression for validation
91216
91217 In addition, custom validations may be added:
91218  
91219 - {@link #vtype} specifies a virtual type implementation from {@link Ext.form.field.VTypes} which can contain
91220   custom validation logic
91221 - {@link #validator} allows a custom arbitrary function to be called during validation
91222
91223 The details around how and when each of these validation options get used are described in the
91224 documentation for {@link #getErrors}.
91225
91226 By default, the field value is checked for validity immediately while the user is typing in the
91227 field. This can be controlled with the {@link #validateOnChange}, {@link #checkChangeEvents}, and
91228 {@link #checkChangeBugger} configurations. Also see the details on Form Validation in the
91229 {@link Ext.form.Panel} class documentation.
91230
91231 #Masking and Character Stripping#
91232
91233 Text fields can be configured with custom regular expressions to be applied to entered values before
91234 validation: see {@link #maskRe} and {@link #stripCharsRe} for details.
91235 {@img Ext.form.Text/Ext.form.Text.png Ext.form.Text component}
91236 #Example usage:#
91237
91238     Ext.create('Ext.form.Panel', {
91239         title: 'Contact Info',
91240         width: 300,
91241         bodyPadding: 10,
91242         renderTo: Ext.getBody(),        
91243         items: [{
91244             xtype: 'textfield',
91245             name: 'name',
91246             fieldLabel: 'Name',
91247             allowBlank: false  // requires a non-empty value
91248         }, {
91249             xtype: 'textfield',
91250             name: 'email',
91251             fieldLabel: 'Email Address',
91252             vtype: 'email'  // requires value to be a valid email address format
91253         }]
91254     }); 
91255
91256  * @constructor Creates a new TextField
91257  * @param {Object} config Configuration options
91258  *
91259  * @xtype textfield
91260  * @markdown
91261  * @docauthor Jason Johnston <jason@sencha.com>
91262  */
91263 Ext.define('Ext.form.field.Text', {
91264     extend:'Ext.form.field.Base',
91265     alias: 'widget.textfield',
91266     requires: ['Ext.form.field.VTypes', 'Ext.layout.component.field.Text'],
91267     alternateClassName: ['Ext.form.TextField', 'Ext.form.Text'],
91268
91269     /**
91270      * @cfg {String} vtypeText A custom error message to display in place of the default message provided
91271      * for the <b><code>{@link #vtype}</code></b> currently set for this field (defaults to <tt>undefined</tt>).
91272      * <b>Note</b>: only applies if <b><code>{@link #vtype}</code></b> is set, else ignored.
91273      */
91274     
91275     /**
91276      * @cfg {RegExp} stripCharsRe A JavaScript RegExp object used to strip unwanted content from the value
91277      * before validation (defaults to <tt>undefined</tt>).
91278      */
91279
91280     /**
91281      * @cfg {Number} size An initial value for the 'size' attribute on the text input element. This is only
91282      * used if the field has no configured {@link #width} and is not given a width by its container's layout.
91283      * Defaults to <tt>20</tt>.
91284      */
91285     size: 20,
91286
91287     /**
91288      * @cfg {Boolean} grow <tt>true</tt> if this field should automatically grow and shrink to its content
91289      * (defaults to <tt>false</tt>)
91290      */
91291
91292     /**
91293      * @cfg {Number} growMin The minimum width to allow when <code><b>{@link #grow}</b> = true</code> (defaults
91294      * to <tt>30</tt>)
91295      */
91296     growMin : 30,
91297     
91298     /**
91299      * @cfg {Number} growMax The maximum width to allow when <code><b>{@link #grow}</b> = true</code> (defaults
91300      * to <tt>800</tt>)
91301      */
91302     growMax : 800,
91303
91304     /**
91305      * @cfg {String} growAppend
91306      * A string that will be appended to the field's current value for the purposes of calculating the target
91307      * field size. Only used when the {@link #grow} config is <tt>true</tt>. Defaults to a single capital "W"
91308      * (the widest character in common fonts) to leave enough space for the next typed character and avoid the
91309      * field value shifting before the width is adjusted.
91310      */
91311     growAppend: 'W',
91312     
91313     /**
91314      * @cfg {String} vtype A validation type name as defined in {@link Ext.form.field.VTypes} (defaults to <tt>undefined</tt>)
91315      */
91316
91317     /**
91318      * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes that do
91319      * not match (defaults to <tt>undefined</tt>)
91320      */
91321
91322     /**
91323      * @cfg {Boolean} disableKeyFilter Specify <tt>true</tt> to disable input keystroke filtering (defaults
91324      * to <tt>false</tt>)
91325      */
91326
91327     /**
91328      * @cfg {Boolean} allowBlank Specify <tt>false</tt> to validate that the value's length is > 0 (defaults to
91329      * <tt>true</tt>)
91330      */
91331     allowBlank : true,
91332     
91333     /**
91334      * @cfg {Number} minLength Minimum input field length required (defaults to <tt>0</tt>)
91335      */
91336     minLength : 0,
91337     
91338     /**
91339      * @cfg {Number} maxLength Maximum input field length allowed by validation (defaults to Number.MAX_VALUE).
91340      * This behavior is intended to provide instant feedback to the user by improving usability to allow pasting
91341      * and editing or overtyping and back tracking. To restrict the maximum number of characters that can be
91342      * entered into the field use the <tt><b>{@link Ext.form.field.Text#enforceMaxLength enforceMaxLength}</b></tt> option.
91343      */
91344     maxLength : Number.MAX_VALUE,
91345     
91346     /**
91347      * @cfg {Boolean} enforceMaxLength True to set the maxLength property on the underlying input field. Defaults to <tt>false</tt>
91348      */
91349
91350     /**
91351      * @cfg {String} minLengthText Error text to display if the <b><tt>{@link #minLength minimum length}</tt></b>
91352      * validation fails (defaults to <tt>'The minimum length for this field is {minLength}'</tt>)
91353      */
91354     minLengthText : 'The minimum length for this field is {0}',
91355     
91356     /**
91357      * @cfg {String} maxLengthText Error text to display if the <b><tt>{@link #maxLength maximum length}</tt></b>
91358      * validation fails (defaults to <tt>'The maximum length for this field is {maxLength}'</tt>)
91359      */
91360     maxLengthText : 'The maximum length for this field is {0}',
91361     
91362     /**
91363      * @cfg {Boolean} selectOnFocus <tt>true</tt> to automatically select any existing field text when the field
91364      * receives input focus (defaults to <tt>false</tt>)
91365      */
91366     
91367     /**
91368      * @cfg {String} blankText The error text to display if the <b><tt>{@link #allowBlank}</tt></b> validation
91369      * fails (defaults to <tt>'This field is required'</tt>)
91370      */
91371     blankText : 'This field is required',
91372     
91373     /**
91374      * @cfg {Function} validator
91375      * <p>A custom validation function to be called during field validation ({@link #getErrors})
91376      * (defaults to <tt>undefined</tt>). If specified, this function will be called first, allowing the
91377      * developer to override the default validation process.</p>
91378      * <br><p>This function will be passed the following Parameters:</p>
91379      * <div class="mdetail-params"><ul>
91380      * <li><code>value</code>: <i>Mixed</i>
91381      * <div class="sub-desc">The current field value</div></li>
91382      * </ul></div>
91383      * <br><p>This function is to Return:</p>
91384      * <div class="mdetail-params"><ul>
91385      * <li><code>true</code>: <i>Boolean</i>
91386      * <div class="sub-desc"><code>true</code> if the value is valid</div></li>
91387      * <li><code>msg</code>: <i>String</i>
91388      * <div class="sub-desc">An error message if the value is invalid</div></li>
91389      * </ul></div>
91390      */
91391
91392     /**
91393      * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation
91394      * (defaults to <tt>undefined</tt>). If the test fails, the field will be marked invalid using
91395      * <b><tt>{@link #regexText}</tt></b>.
91396      */
91397
91398     /**
91399      * @cfg {String} regexText The error text to display if <b><tt>{@link #regex}</tt></b> is used and the
91400      * test fails during validation (defaults to <tt>''</tt>)
91401      */
91402     regexText : '',
91403     
91404     /**
91405      * @cfg {String} emptyText
91406      * <p>The default text to place into an empty field (defaults to <tt>undefined</tt>).</p>
91407      * <p>Note that normally this value will be submitted to the server if this field is enabled; to prevent this
91408      * you can set the {@link Ext.form.action.Action#submitEmptyText submitEmptyText} option of
91409      * {@link Ext.form.Basic#submit} to <tt>false</tt>.</p>
91410      * <p>Also note that if you use <tt>{@link #inputType inputType}:'file'</tt>, {@link #emptyText} is not
91411      * supported and should be avoided.</p>
91412      */
91413
91414     /**
91415      * @cfg {String} emptyCls The CSS class to apply to an empty field to style the <b><tt>{@link #emptyText}</tt></b>
91416      * (defaults to <tt>'x-form-empty-field'</tt>).  This class is automatically added and removed as needed
91417      * depending on the current field value.
91418      */
91419     emptyCls : Ext.baseCSSPrefix + 'form-empty-field',
91420
91421     ariaRole: 'textbox',
91422
91423     /**
91424      * @cfg {Boolean} enableKeyEvents <tt>true</tt> to enable the proxying of key events for the HTML input field (defaults to <tt>false</tt>)
91425      */
91426
91427     componentLayout: 'textfield',
91428
91429     initComponent : function(){
91430         this.callParent();
91431         this.addEvents(
91432             /**
91433              * @event autosize
91434              * Fires when the <tt><b>{@link #autoSize}</b></tt> function is triggered and the field is
91435              * resized according to the {@link #grow}/{@link #growMin}/{@link #growMax} configs as a result.
91436              * This event provides a hook for the developer to apply additional logic at runtime to resize the
91437              * field if needed.
91438              * @param {Ext.form.field.Text} this This text field
91439              * @param {Number} width The new field width
91440              */
91441             'autosize',
91442
91443             /**
91444              * @event keydown
91445              * Keydown input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
91446              * is set to true.
91447              * @param {Ext.form.field.Text} this This text field
91448              * @param {Ext.EventObject} e
91449              */
91450             'keydown',
91451             /**
91452              * @event keyup
91453              * Keyup input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
91454              * is set to true.
91455              * @param {Ext.form.field.Text} this This text field
91456              * @param {Ext.EventObject} e
91457              */
91458             'keyup',
91459             /**
91460              * @event keypress
91461              * Keypress input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
91462              * is set to true.
91463              * @param {Ext.form.field.Text} this This text field
91464              * @param {Ext.EventObject} e
91465              */
91466             'keypress'
91467         );
91468     },
91469
91470     // private
91471     initEvents : function(){
91472         var me = this,
91473             el = me.inputEl;
91474         
91475         me.callParent();
91476         if(me.selectOnFocus || me.emptyText){
91477             me.mon(el, 'mousedown', me.onMouseDown, me);
91478         }
91479         if(me.maskRe || (me.vtype && me.disableKeyFilter !== true && (me.maskRe = Ext.form.field.VTypes[me.vtype+'Mask']))){
91480             me.mon(el, 'keypress', me.filterKeys, me);
91481         }
91482
91483         if (me.enableKeyEvents) {
91484             me.mon(el, {
91485                 scope: me,
91486                 keyup: me.onKeyUp,
91487                 keydown: me.onKeyDown,
91488                 keypress: me.onKeyPress
91489             });
91490         }
91491     },
91492
91493     /**
91494      * @private override - treat undefined and null values as equal to an empty string value
91495      */
91496     isEqual: function(value1, value2) {
91497         return String(Ext.value(value1, '')) === String(Ext.value(value2, ''));
91498     },
91499
91500     /**
91501      * @private
91502      * If grow=true, invoke the autoSize method when the field's value is changed.
91503      */
91504     onChange: function() {
91505         this.callParent();
91506         this.autoSize();
91507     },
91508     
91509     afterRender: function(){
91510         var me = this;
91511         if (me.enforceMaxLength) {
91512             me.inputEl.dom.maxLength = me.maxLength;
91513         }
91514         me.applyEmptyText();
91515         me.autoSize();
91516         me.callParent();
91517     },
91518
91519     onMouseDown: function(e){
91520         var me = this;
91521         if(!me.hasFocus){
91522             me.mon(me.inputEl, 'mouseup', Ext.emptyFn, me, { single: true, preventDefault: true });
91523         }
91524     },
91525
91526     /**
91527      * Performs any necessary manipulation of a raw String value to prepare it for {@link #stringToValue conversion}
91528      * and/or {@link #validate validation}. For text fields this applies the configured {@link #stripCharsRe} to the
91529      * raw value.
91530      * @param {String} value The unprocessed string value
91531      * @return {String} The processed string value
91532      */
91533     processRawValue: function(value) {
91534         var me = this,
91535             stripRe = me.stripCharsRe,
91536             newValue;
91537             
91538         if (stripRe) {
91539             newValue = value.replace(stripRe, '');
91540             if (newValue !== value) {
91541                 me.setRawValue(newValue);
91542                 value = newValue;
91543             }
91544         }
91545         return value;
91546     },
91547
91548     //private
91549     onDisable: function(){
91550         this.callParent();
91551         if (Ext.isIE) {
91552             this.inputEl.dom.unselectable = 'on';
91553         }
91554     },
91555
91556     //private
91557     onEnable: function(){
91558         this.callParent();
91559         if (Ext.isIE) {
91560             this.inputEl.dom.unselectable = '';
91561         }
91562     },
91563
91564     onKeyDown: function(e) {
91565         this.fireEvent('keydown', this, e);
91566     },
91567
91568     onKeyUp: function(e) {
91569         this.fireEvent('keyup', this, e);
91570     },
91571
91572     onKeyPress: function(e) {
91573         this.fireEvent('keypress', this, e);
91574     },
91575
91576     /**
91577      * Resets the current field value to the originally-loaded value and clears any validation messages.
91578      * Also adds <tt><b>{@link #emptyText}</b></tt> and <tt><b>{@link #emptyCls}</b></tt> if the
91579      * original value was blank.
91580      */
91581     reset : function(){
91582         this.callParent();
91583         this.applyEmptyText();
91584     },
91585
91586     applyEmptyText : function(){
91587         var me = this,
91588             emptyText = me.emptyText,
91589             isEmpty;
91590
91591         if (me.rendered && emptyText) {
91592             isEmpty = me.getRawValue().length < 1 && !me.hasFocus;
91593             
91594             if (Ext.supports.Placeholder) {
91595                 me.inputEl.dom.placeholder = emptyText;
91596             } else if (isEmpty) {
91597                 me.setRawValue(emptyText);
91598             }
91599             
91600             //all browsers need this because of a styling issue with chrome + placeholders.
91601             //the text isnt vertically aligned when empty (and using the placeholder)
91602             if (isEmpty) {
91603                 me.inputEl.addCls(me.emptyCls);
91604             }
91605
91606             me.autoSize();
91607         }
91608     },
91609
91610     // private
91611     preFocus : function(){
91612         var me = this,
91613             inputEl = me.inputEl,
91614             emptyText = me.emptyText,
91615             isEmpty;
91616
91617         if (emptyText && !Ext.supports.Placeholder && inputEl.dom.value === emptyText) {
91618             me.setRawValue('');
91619             isEmpty = true;
91620             inputEl.removeCls(me.emptyCls);
91621         } else if (Ext.supports.Placeholder) {
91622             me.inputEl.removeCls(me.emptyCls);
91623         }
91624         if (me.selectOnFocus || isEmpty) {
91625             inputEl.dom.select();
91626         }
91627     },
91628
91629     onFocus: function() {
91630         var me = this;
91631         me.callParent(arguments);
91632         if (me.emptyText) {
91633             me.autoSize();
91634         }
91635     },
91636
91637     // private
91638     postBlur : function(){
91639         this.applyEmptyText();
91640     },
91641
91642     // private
91643     filterKeys : function(e){
91644         if(e.ctrlKey){
91645             return;
91646         }
91647         var key = e.getKey(),
91648             charCode = String.fromCharCode(e.getCharCode());
91649             
91650         if(Ext.isGecko && (e.isNavKeyPress() || key === e.BACKSPACE || (key === e.DELETE && e.button === -1))){
91651             return;
91652         }
91653         
91654         if(!Ext.isGecko && e.isSpecialKey() && !charCode){
91655             return;
91656         }
91657         if(!this.maskRe.test(charCode)){
91658             e.stopEvent();
91659         }
91660     },
91661
91662     /**
91663      * Returns the raw String value of the field, without performing any normalization, conversion, or validation.
91664      * Gets the current value of the input element if the field has been rendered, ignoring the value if it is the
91665      * {@link #emptyText}. To get a normalized and converted value see {@link #getValue}.
91666      * @return {String} value The raw String value of the field
91667      */
91668     getRawValue: function() {
91669         var me = this,
91670             v = me.callParent();
91671         if (v === me.emptyText) {
91672             v = '';
91673         }
91674         return v;
91675     },
91676
91677     /**
91678      * Sets a data value into the field and runs the change detection and validation. Also applies any configured
91679      * {@link #emptyText} for text fields. To set the value directly without these inspections see {@link #setRawValue}.
91680      * @param {Mixed} value The value to set
91681      * @return {Ext.form.field.Text} this
91682      */
91683     setValue: function(value) {
91684         var me = this,
91685             inputEl = me.inputEl;
91686         
91687         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
91688             inputEl.removeCls(me.emptyCls);
91689         }
91690         
91691         me.callParent(arguments);
91692
91693         me.applyEmptyText();
91694         return me;
91695     },
91696
91697     /**
91698 Validates a value according to the field's validation rules and returns an array of errors
91699 for any failing validations. Validation rules are processed in the following order:
91700
91701 1. **Field specific validator**
91702     
91703     A validator offers a way to customize and reuse a validation specification.
91704     If a field is configured with a `{@link #validator}`
91705     function, it will be passed the current field value.  The `{@link #validator}`
91706     function is expected to return either:
91707     
91708     - Boolean `true`  if the value is valid (validation continues).
91709     - a String to represent the invalid message if invalid (validation halts).
91710
91711 2. **Basic Validation**
91712
91713     If the `{@link #validator}` has not halted validation,
91714     basic validation proceeds as follows:
91715     
91716     - `{@link #allowBlank}` : (Invalid message = `{@link #emptyText}`)
91717     
91718         Depending on the configuration of <code>{@link #allowBlank}</code>, a
91719         blank field will cause validation to halt at this step and return
91720         Boolean true or false accordingly.
91721     
91722     - `{@link #minLength}` : (Invalid message = `{@link #minLengthText}`)
91723
91724         If the passed value does not satisfy the `{@link #minLength}`
91725         specified, validation halts.
91726
91727     -  `{@link #maxLength}` : (Invalid message = `{@link #maxLengthText}`)
91728
91729         If the passed value does not satisfy the `{@link #maxLength}`
91730         specified, validation halts.
91731
91732 3. **Preconfigured Validation Types (VTypes)**
91733
91734     If none of the prior validation steps halts validation, a field
91735     configured with a `{@link #vtype}` will utilize the
91736     corresponding {@link Ext.form.field.VTypes VTypes} validation function.
91737     If invalid, either the field's `{@link #vtypeText}` or
91738     the VTypes vtype Text property will be used for the invalid message.
91739     Keystrokes on the field will be filtered according to the VTypes
91740     vtype Mask property.
91741
91742 4. **Field specific regex test**
91743
91744     If none of the prior validation steps halts validation, a field's
91745     configured <code>{@link #regex}</code> test will be processed.
91746     The invalid message for this test is configured with `{@link #regexText}`
91747
91748      * @param {Mixed} value The value to validate. The processed raw value will be used if nothing is passed
91749      * @return {Array} Array of any validation errors
91750      * @markdown
91751      */
91752     getErrors: function(value) {
91753         var me = this,
91754             errors = me.callParent(arguments),
91755             validator = me.validator,
91756             emptyText = me.emptyText,
91757             allowBlank = me.allowBlank,
91758             vtype = me.vtype,
91759             vtypes = Ext.form.field.VTypes,
91760             regex = me.regex,
91761             format = Ext.String.format,
91762             msg;
91763
91764         value = value || me.processRawValue(me.getRawValue());
91765
91766         if (Ext.isFunction(validator)) {
91767             msg = validator.call(me, value);
91768             if (msg !== true) {
91769                 errors.push(msg);
91770             }
91771         }
91772
91773         if (value.length < 1 || value === emptyText) {
91774             if (!allowBlank) {
91775                 errors.push(me.blankText);
91776             }
91777             //if value is blank, there cannot be any additional errors
91778             return errors;
91779         }
91780
91781         if (value.length < me.minLength) {
91782             errors.push(format(me.minLengthText, me.minLength));
91783         }
91784
91785         if (value.length > me.maxLength) {
91786             errors.push(format(me.maxLengthText, me.maxLength));
91787         }
91788
91789         if (vtype) {
91790             if(!vtypes[vtype](value, me)){
91791                 errors.push(me.vtypeText || vtypes[vtype +'Text']);
91792             }
91793         }
91794
91795         if (regex && !regex.test(value)) {
91796             errors.push(me.regexText || me.invalidText);
91797         }
91798
91799         return errors;
91800     },
91801
91802     /**
91803      * Selects text in this field
91804      * @param {Number} start (optional) The index where the selection should start (defaults to 0)
91805      * @param {Number} end (optional) The index where the selection should end (defaults to the text length)
91806      */
91807     selectText : function(start, end){
91808         var me = this,
91809             v = me.getRawValue(),
91810             doFocus = true,
91811             el = me.inputEl.dom,
91812             undef,
91813             range;
91814             
91815         if (v.length > 0) {
91816             start = start === undef ? 0 : start;
91817             end = end === undef ? v.length : end;
91818             if (el.setSelectionRange) {
91819                 el.setSelectionRange(start, end);
91820             }
91821             else if(el.createTextRange) {
91822                 range = el.createTextRange();
91823                 range.moveStart('character', start);
91824                 range.moveEnd('character', end - v.length);
91825                 range.select();
91826             }
91827             doFocus = Ext.isGecko || Ext.isOpera;
91828         }
91829         if (doFocus) {
91830             me.focus();
91831         }
91832     },
91833
91834     /**
91835      * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed.
91836      * This only takes effect if <tt>{@link #grow} = true</tt>, and fires the {@link #autosize} event if the
91837      * width changes.
91838      */
91839     autoSize: function() {
91840         var me = this,
91841             width;
91842         if (me.grow && me.rendered) {
91843             me.doComponentLayout();
91844             width = me.inputEl.getWidth();
91845             if (width !== me.lastInputWidth) {
91846                 me.fireEvent('autosize', width);
91847                 me.lastInputWidth = width;
91848             }
91849         }
91850     },
91851
91852     initAria: function() {
91853         this.callParent();
91854         this.getActionEl().dom.setAttribute('aria-required', this.allowBlank === false);
91855     },
91856
91857     /**
91858      * @protected override
91859      * To get the natural width of the inputEl, we do a simple calculation based on the
91860      * 'size' config. We use hard-coded numbers to approximate what browsers do natively,
91861      * to avoid having to read any styles which would hurt performance.
91862      */
91863     getBodyNaturalWidth: function() {
91864         return Math.round(this.size * 6.5) + 20;
91865     }
91866
91867 });
91868
91869 /**
91870  * @class Ext.form.field.TextArea
91871  * @extends Ext.form.field.Text
91872
91873 This class creates a multiline text field, which can be used as a direct replacement for traditional 
91874 textarea fields. In addition, it supports automatically {@link #grow growing} the height of the textarea to 
91875 fit its content.
91876
91877 All of the configuration options from {@link Ext.form.field.Text} can be used on TextArea.
91878 {@img Ext.form.TextArea/Ext.form.TextArea.png Ext.form.TextArea component}
91879 Example usage:
91880
91881     Ext.create('Ext.form.FormPanel', {
91882         title      : 'Sample TextArea',
91883         width      : 400,
91884         bodyPadding: 10,
91885         renderTo   : Ext.getBody(),
91886         items: [{
91887             xtype     : 'textareafield',
91888             grow      : true,
91889             name      : 'message',
91890             fieldLabel: 'Message',
91891             anchor    : '100%'
91892         }]
91893     }); 
91894
91895 Some other useful configuration options when using {@link #grow} are {@link #growMin} and {@link #growMax}. These 
91896 allow you to set the minimum and maximum grow heights for the textarea.
91897
91898  * @constructor
91899  * Creates a new TextArea
91900  * @param {Object} config Configuration options
91901  * @xtype textareafield
91902  * @docauthor Robert Dougan <rob@sencha.com>
91903  */
91904 Ext.define('Ext.form.field.TextArea', {
91905     extend:'Ext.form.field.Text',
91906     alias: ['widget.textareafield', 'widget.textarea'],
91907     alternateClassName: 'Ext.form.TextArea',
91908     requires: ['Ext.XTemplate', 'Ext.layout.component.field.TextArea'],
91909
91910     fieldSubTpl: [
91911         '<textarea id="{id}" ',
91912             '<tpl if="name">name="{name}" </tpl>',
91913             '<tpl if="rows">rows="{rows}" </tpl>',
91914             '<tpl if="cols">cols="{cols}" </tpl>',
91915             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
91916             'class="{fieldCls} {typeCls}" ',
91917             'autocomplete="off">',
91918         '</textarea>',
91919         {
91920             compiled: true,
91921             disableFormats: true
91922         }
91923     ],
91924
91925     /**
91926      * @cfg {Number} growMin The minimum height to allow when <tt>{@link Ext.form.field.Text#grow grow}=true</tt>
91927      * (defaults to <tt>60</tt>)
91928      */
91929     growMin: 60,
91930
91931     /**
91932      * @cfg {Number} growMax The maximum height to allow when <tt>{@link Ext.form.field.Text#grow grow}=true</tt>
91933      * (defaults to <tt>1000</tt>)
91934      */
91935     growMax: 1000,
91936
91937     /**
91938      * @cfg {String} growAppend
91939      * A string that will be appended to the field's current value for the purposes of calculating the target
91940      * field size. Only used when the {@link #grow} config is <tt>true</tt>. Defaults to a newline for TextArea
91941      * to ensure there is always a space below the current line.
91942      */
91943     growAppend: '\n-',
91944
91945     /**
91946      * @cfg {Number} cols An initial value for the 'cols' attribute on the textarea element. This is only
91947      * used if the component has no configured {@link #width} and is not given a width by its container's
91948      * layout. Defaults to <tt>20</tt>.
91949      */
91950     cols: 20,
91951
91952     /**
91953      * @cfg {Number} cols An initial value for the 'cols' attribute on the textarea element. This is only
91954      * used if the component has no configured {@link #width} and is not given a width by its container's
91955      * layout. Defaults to <tt>4</tt>.
91956      */
91957     rows: 4,
91958
91959     /**
91960      * @cfg {Boolean} enterIsSpecial
91961      * True if you want the enter key to be classed as a <tt>special</tt> key. Special keys are generally navigation
91962      * keys (arrows, space, enter). Setting the config property to <tt>true</tt> would mean that you could not insert
91963      * returns into the textarea.
91964      * (defaults to <tt>false</tt>)
91965      */
91966     enterIsSpecial: false,
91967
91968     /**
91969      * @cfg {Boolean} preventScrollbars <tt>true</tt> to prevent scrollbars from appearing regardless of how much text is
91970      * in the field. This option is only relevant when {@link #grow} is <tt>true</tt>. Equivalent to setting overflow: hidden, defaults to
91971      * <tt>false</tt>.
91972      */
91973     preventScrollbars: false,
91974
91975     // private
91976     componentLayout: 'textareafield',
91977
91978     // private
91979     onRender: function(ct, position) {
91980         var me = this;
91981         Ext.applyIf(me.subTplData, {
91982             cols: me.cols,
91983             rows: me.rows
91984         });
91985
91986         me.callParent(arguments);
91987     },
91988
91989     // private
91990     afterRender: function(){
91991         var me = this;
91992
91993         me.callParent(arguments);
91994
91995         if (me.grow) {
91996             if (me.preventScrollbars) {
91997                 me.inputEl.setStyle('overflow', 'hidden');
91998             }
91999             me.inputEl.setHeight(me.growMin);
92000         }
92001     },
92002
92003     // private
92004     fireKey: function(e) {
92005         if (e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() !== e.ENTER || e.hasModifier()))) {
92006             this.fireEvent('specialkey', this, e);
92007         }
92008     },
92009
92010     /**
92011      * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed.
92012      * This only takes effect if <tt>{@link #grow} = true</tt>, and fires the {@link #autosize} event if
92013      * the height changes.
92014      */
92015     autoSize: function() {
92016         var me = this,
92017             height;
92018
92019         if (me.grow && me.rendered) {
92020             me.doComponentLayout();
92021             height = me.inputEl.getHeight();
92022             if (height !== me.lastInputHeight) {
92023                 me.fireEvent('autosize', height);
92024                 me.lastInputHeight = height;
92025             }
92026         }
92027     },
92028
92029     // private
92030     initAria: function() {
92031         this.callParent(arguments);
92032         this.getActionEl().dom.setAttribute('aria-multiline', true);
92033     },
92034
92035     /**
92036      * @protected override
92037      * To get the natural width of the textarea element, we do a simple calculation based on the
92038      * 'cols' config. We use hard-coded numbers to approximate what browsers do natively,
92039      * to avoid having to read any styles which would hurt performance.
92040      */
92041     getBodyNaturalWidth: function() {
92042         return Math.round(this.cols * 6.5) + 20;
92043     }
92044
92045 });
92046
92047
92048 /**
92049  * @class Ext.window.MessageBox
92050  * @extends Ext.window.Window
92051
92052 Utility class for generating different styles of message boxes.  The singleton instance, `Ext.Msg` can also be used.
92053 Note that a MessageBox is asynchronous.  Unlike a regular JavaScript `alert` (which will halt
92054 browser execution), showing a MessageBox will not cause the code to stop.  For this reason, if you have code
92055 that should only run *after* some user feedback from the MessageBox, you must use a callback function
92056 (see the `function` parameter for {@link #show} for more details).
92057
92058 {@img Ext.window.MessageBox/messagebox1.png alert MessageBox}
92059 {@img Ext.window.MessageBox/messagebox2.png prompt MessageBox}
92060 {@img Ext.window.MessageBox/messagebox3.png show MessageBox}
92061 #Example usage:#
92062
92063     // Basic alert:
92064     Ext.Msg.alert('Status', 'Changes saved successfully.');
92065
92066     // Prompt for user data and process the result using a callback:
92067     Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
92068         if (btn == 'ok'){
92069             // process text value and close...
92070         }
92071     });
92072
92073     // Show a dialog using config options:
92074     Ext.Msg.show({
92075          title:'Save Changes?',
92076          msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
92077          buttons: Ext.Msg.YESNOCANCEL,
92078          fn: processResult,
92079          animateTarget: 'elId',
92080          icon: Ext.window.MessageBox.QUESTION
92081     });
92082
92083  * @markdown
92084  * @singleton
92085  * @xtype messagebox
92086  */
92087 Ext.define('Ext.window.MessageBox', {
92088     extend: 'Ext.window.Window',
92089
92090     requires: [
92091         'Ext.toolbar.Toolbar',
92092         'Ext.form.field.Text',
92093         'Ext.form.field.TextArea',
92094         'Ext.button.Button',
92095         'Ext.layout.container.Anchor',
92096         'Ext.layout.container.HBox',
92097         'Ext.ProgressBar'
92098     ],
92099
92100     alternateClassName: 'Ext.MessageBox',
92101
92102     alias: 'widget.messagebox',
92103
92104     /**
92105      * Button config that displays a single OK button
92106      * @type Number
92107      */
92108     OK : 1,
92109     /**
92110      * Button config that displays a single Yes button
92111      * @type Number
92112      */
92113     YES : 2,
92114     /**
92115      * Button config that displays a single No button
92116      * @type Number
92117      */
92118     NO : 4,
92119     /**
92120      * Button config that displays a single Cancel button
92121      * @type Number
92122      */
92123     CANCEL : 8,
92124     /**
92125      * Button config that displays OK and Cancel buttons
92126      * @type Number
92127      */
92128     OKCANCEL : 9,
92129     /**
92130      * Button config that displays Yes and No buttons
92131      * @type Number
92132      */
92133     YESNO : 6,
92134     /**
92135      * Button config that displays Yes, No and Cancel buttons
92136      * @type Number
92137      */
92138     YESNOCANCEL : 14,
92139     /**
92140      * The CSS class that provides the INFO icon image
92141      * @type String
92142      */
92143     INFO : 'ext-mb-info',
92144     /**
92145      * The CSS class that provides the WARNING icon image
92146      * @type String
92147      */
92148     WARNING : 'ext-mb-warning',
92149     /**
92150      * The CSS class that provides the QUESTION icon image
92151      * @type String
92152      */
92153     QUESTION : 'ext-mb-question',
92154     /**
92155      * The CSS class that provides the ERROR icon image
92156      * @type String
92157      */
92158     ERROR : 'ext-mb-error',
92159
92160     // hide it by offsets. Windows are hidden on render by default.
92161     hideMode: 'offsets',
92162     closeAction: 'hide',
92163     resizable: false,
92164     title: '&#160;',
92165
92166     width: 600,
92167     height: 500,
92168     minWidth: 250,
92169     maxWidth: 600,
92170     minHeight: 110,
92171     maxHeight: 500,
92172     constrain: true,
92173
92174     cls: Ext.baseCSSPrefix + 'message-box',
92175
92176     layout: {
92177         type: 'anchor'
92178     },
92179
92180     /**
92181      * The default height in pixels of the message box's multiline textarea if displayed (defaults to 75)
92182      * @type Number
92183      */
92184     defaultTextHeight : 75,
92185     /**
92186      * The minimum width in pixels of the message box if it is a progress-style dialog.  This is useful
92187      * for setting a different minimum width than text-only dialogs may need (defaults to 250).
92188      * @type Number
92189      */
92190     minProgressWidth : 250,
92191     /**
92192      * The minimum width in pixels of the message box if it is a prompt dialog.  This is useful
92193      * for setting a different minimum width than text-only dialogs may need (defaults to 250).
92194      * @type Number
92195      */
92196     minPromptWidth: 250,
92197     /**
92198      * An object containing the default button text strings that can be overriden for localized language support.
92199      * Supported properties are: ok, cancel, yes and no.  Generally you should include a locale-specific
92200      * resource file for handling language support across the framework.
92201      * Customize the default text like so: Ext.window.MessageBox.buttonText.yes = "oui"; //french
92202      * @type Object
92203      */
92204     buttonText: {
92205         ok: 'OK',
92206         yes: 'Yes',
92207         no: 'No',
92208         cancel: 'Cancel'
92209     },
92210
92211     buttonIds: [
92212         'ok', 'yes', 'no', 'cancel'
92213     ],
92214
92215     titleText: {
92216         confirm: 'Confirm',
92217         prompt: 'Prompt',
92218         wait: 'Loading...',
92219         alert: 'Attention'
92220     },
92221
92222     iconHeight: 35,
92223
92224     makeButton: function(btnIdx) {
92225         var btnId = this.buttonIds[btnIdx];
92226         return Ext.create('Ext.button.Button', {
92227             handler: this.btnCallback,
92228             itemId: btnId,
92229             scope: this,
92230             text: this.buttonText[btnId],
92231             minWidth: 75
92232         });
92233     },
92234
92235     btnCallback: function(btn) {
92236         var me = this,
92237             value,
92238             field;
92239
92240         if (me.cfg.prompt || me.cfg.multiline) {
92241             if (me.cfg.multiline) {
92242                 field = me.textArea;
92243             } else {
92244                 field = me.textField;
92245             }
92246             value = field.getValue();
92247             field.reset();
92248         }
92249
92250         // Important not to have focus remain in the hidden Window; Interferes with DnD.
92251         btn.blur();
92252         me.hide();
92253         me.userCallback(btn.itemId, value, me.cfg);
92254     },
92255
92256     hide: function() {
92257         var me = this;
92258         me.dd.endDrag();
92259         me.progressBar.reset();
92260         me.removeCls(me.cfg.cls);
92261         me.callParent();
92262     },
92263
92264     initComponent: function() {
92265         var me = this,
92266             i, button;
92267
92268         me.title = '&#160;';
92269
92270         me.topContainer = Ext.create('Ext.container.Container', {
92271             anchor: '100%',
92272             style: {
92273                 padding: '10px',
92274                 overflow: 'hidden'
92275             },
92276             items: [
92277                 me.iconComponent = Ext.create('Ext.Component', {
92278                     cls: 'ext-mb-icon',
92279                     width: 50,
92280                     height: me.iconHeight,
92281                     style: {
92282                         'float': 'left'
92283                     }
92284                 }),
92285                 me.promptContainer = Ext.create('Ext.container.Container', {
92286                     layout: {
92287                         type: 'anchor'
92288                     },
92289                     items: [
92290                         me.msg = Ext.create('Ext.Component', {
92291                             autoEl: { tag: 'span' },
92292                             cls: 'ext-mb-text'
92293                         }),
92294                         me.textField = Ext.create('Ext.form.field.Text', {
92295                             anchor: '100%',
92296                             enableKeyEvents: true,
92297                             listeners: {
92298                                 keydown: me.onPromptKey,
92299                                 scope: me
92300                             }
92301                         }),
92302                         me.textArea = Ext.create('Ext.form.field.TextArea', {
92303                             anchor: '100%',
92304                             height: 75
92305                         })
92306                     ]
92307                 })
92308             ]
92309         });
92310         me.progressBar = Ext.create('Ext.ProgressBar', {
92311             anchor: '-10',
92312             style: 'margin-left:10px'
92313         });
92314
92315         me.items = [me.topContainer, me.progressBar];
92316
92317         // Create the buttons based upon passed bitwise config
92318         me.msgButtons = [];
92319         for (i = 0; i < 4; i++) {
92320             button = me.makeButton(i);
92321             me.msgButtons[button.itemId] = button;
92322             me.msgButtons.push(button);
92323         }
92324         me.bottomTb = Ext.create('Ext.toolbar.Toolbar', {
92325             ui: 'footer',
92326             dock: 'bottom',
92327             layout: {
92328                 pack: 'center'
92329             },
92330             items: [
92331                 me.msgButtons[0],
92332                 me.msgButtons[1],
92333                 me.msgButtons[2],
92334                 me.msgButtons[3]
92335             ]
92336         });
92337         me.dockedItems = [me.bottomTb];
92338
92339         me.callParent();
92340     },
92341
92342     onPromptKey: function(textField, e) {
92343         var me = this,
92344             blur;
92345
92346         if (e.keyCode === Ext.EventObject.RETURN || e.keyCode === 10) {
92347             if (me.msgButtons.ok.isVisible()) {
92348                 blur = true;
92349                 me.msgButtons.ok.handler.call(me, me.msgButtons.ok);
92350             } else if (me.msgButtons.yes.isVisible()) {
92351                 me.msgButtons.yes.handler.call(me, me.msgButtons.yes);
92352                 blur = true;
92353             }
92354
92355             if (blur) {
92356                 me.textField.blur();
92357             }
92358         }
92359     },
92360
92361     reconfigure: function(cfg) {
92362         var me = this,
92363             buttons = cfg.buttons || 0,
92364             hideToolbar = true,
92365             initialWidth = me.maxWidth,
92366             i;
92367
92368         cfg = cfg || {};
92369         me.cfg = cfg;
92370         if (cfg.width) {
92371             initialWidth = cfg.width;
92372         }
92373
92374         // Default to allowing the Window to take focus.
92375         delete me.defaultFocus;
92376
92377         // clear any old animateTarget
92378         me.animateTarget = cfg.animateTarget || undefined;
92379
92380         // Defaults to modal
92381         me.modal = cfg.modal !== false;
92382
92383         // Show the title
92384         if (cfg.title) {
92385             me.setTitle(cfg.title||'&#160;');
92386         }
92387
92388         if (!me.rendered) {
92389             me.width = initialWidth;
92390             me.render(Ext.getBody());
92391         } else {
92392             me.hidden = false;
92393             me.setSize(initialWidth, me.maxHeight);
92394         }
92395         me.setPosition(-10000, -10000);
92396
92397         // Hide or show the close tool
92398         me.closable = cfg.closable && !cfg.wait;
92399         if (cfg.closable === false) {
92400             me.tools.close.hide();
92401         } else {
92402             me.tools.close.show();
92403         }
92404
92405         // Hide or show the header
92406         if (!cfg.title && !me.closable) {
92407             me.header.hide();
92408         } else {
92409             me.header.show();
92410         }
92411
92412         // Default to dynamic drag: drag the window, not a ghost
92413         me.liveDrag = !cfg.proxyDrag;
92414
92415         // wrap the user callback
92416         me.userCallback = Ext.Function.bind(cfg.callback ||cfg.fn || Ext.emptyFn, cfg.scope || Ext.global);
92417
92418         // Hide or show the icon Component
92419         me.setIcon(cfg.icon);
92420
92421         // Hide or show the message area
92422         if (cfg.msg) {
92423             me.msg.update(cfg.msg);
92424             me.msg.show();
92425         } else {
92426             me.msg.hide();
92427         }
92428
92429         // Hide or show the input field
92430         if (cfg.prompt || cfg.multiline) {
92431             me.multiline = cfg.multiline;
92432             if (cfg.multiline) {
92433                 me.textArea.setValue(cfg.value);
92434                 me.textArea.setHeight(cfg.defaultTextHeight || me.defaultTextHeight);
92435                 me.textArea.show();
92436                 me.textField.hide();
92437                 me.defaultFocus = me.textArea;
92438             } else {
92439                 me.textField.setValue(cfg.value);
92440                 me.textArea.hide();
92441                 me.textField.show();
92442                 me.defaultFocus = me.textField;
92443             }
92444         } else {
92445             me.textArea.hide();
92446             me.textField.hide();
92447         }
92448
92449         // Hide or show the progress bar
92450         if (cfg.progress || cfg.wait) {
92451             me.progressBar.show();
92452             me.updateProgress(0, cfg.progressText);
92453             if(cfg.wait === true){
92454                 me.progressBar.wait(cfg.waitConfig);
92455             }
92456         } else {
92457             me.progressBar.hide();
92458         }
92459
92460         // Hide or show buttons depending on flag value sent.
92461         for (i = 0; i < 4; i++) {
92462             if (buttons & Math.pow(2, i)) {
92463
92464                 // Default to focus on the first visible button if focus not already set
92465                 if (!me.defaultFocus) {
92466                     me.defaultFocus = me.msgButtons[i];
92467                 }
92468                 me.msgButtons[i].show();
92469                 hideToolbar = false;
92470             } else {
92471                 me.msgButtons[i].hide();
92472             }
92473         }
92474
92475         // Hide toolbar if no buttons to show
92476         if (hideToolbar) {
92477             me.bottomTb.hide();
92478         } else {
92479             me.bottomTb.show();
92480         }
92481         me.hidden = true;
92482     },
92483
92484     /**
92485      * Displays a new message box, or reinitializes an existing message box, based on the config options
92486      * passed in. All display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally,
92487      * although those calls are basic shortcuts and do not support all of the config options allowed here.
92488      * @param {Object} config The following config options are supported: <ul>
92489      * <li><b>animateTarget</b> : String/Element<div class="sub-desc">An id or Element from which the message box should animate as it
92490      * opens and closes (defaults to undefined)</div></li>
92491      * <li><b>buttons</b> : Number<div class="sub-desc">A bitwise button specifier consisting of the sum of any of the following constants:<ul>
92492      * <li>Ext.window.MessageBox.OK</li>
92493      * <li>Ext.window.MessageBox.YES</li>
92494      * <li>Ext.window.MessageBox.NO</li>
92495      * <li>Ext.window.MessageBox.CANCEL</li>
92496      * </ul>Or false to not show any buttons (defaults to false)</div></li>
92497      * <li><b>closable</b> : Boolean<div class="sub-desc">False to hide the top-right close button (defaults to true). Note that
92498      * progress and wait dialogs will ignore this property and always hide the close button as they can only
92499      * be closed programmatically.</div></li>
92500      * <li><b>cls</b> : String<div class="sub-desc">A custom CSS class to apply to the message box's container element</div></li>
92501      * <li><b>defaultTextHeight</b> : Number<div class="sub-desc">The default height in pixels of the message box's multiline textarea
92502      * if displayed (defaults to 75)</div></li>
92503      * <li><b>fn</b> : Function<div class="sub-desc">A callback function which is called when the dialog is dismissed either
92504      * by clicking on the configured buttons, or on the dialog close button, or by pressing
92505      * the return button to enter input.
92506      * <p>Progress and wait dialogs will ignore this option since they do not respond to user
92507      * actions and can only be closed programmatically, so any required function should be called
92508      * by the same code after it closes the dialog. Parameters passed:<ul>
92509      * <li><b>buttonId</b> : String<div class="sub-desc">The ID of the button pressed, one of:<div class="sub-desc"><ul>
92510      * <li><tt>ok</tt></li>
92511      * <li><tt>yes</tt></li>
92512      * <li><tt>no</tt></li>
92513      * <li><tt>cancel</tt></li>
92514      * </ul></div></div></li>
92515      * <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>
92516      * or <tt><a href="#show-option-multiline" ext:member="show-option-multiline" ext:cls="Ext.window.MessageBox">multiline</a></tt> is true</div></li>
92517      * <li><b>opt</b> : Object<div class="sub-desc">The config object passed to show.</div></li>
92518      * </ul></p></div></li>
92519      * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code>this</code> reference) in which the function will be executed.</div></li>
92520      * <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
92521      * dialog (e.g. Ext.window.MessageBox.WARNING or 'custom-class') (defaults to '')</div></li>
92522      * <li><b>iconCls</b> : String<div class="sub-desc">The standard {@link Ext.window.Window#iconCls} to
92523      * add an optional header icon (defaults to '')</div></li>
92524      * <li><b>maxWidth</b> : Number<div class="sub-desc">The maximum width in pixels of the message box (defaults to 600)</div></li>
92525      * <li><b>minWidth</b> : Number<div class="sub-desc">The minimum width in pixels of the message box (defaults to 100)</div></li>
92526      * <li><b>modal</b> : Boolean<div class="sub-desc">False to allow user interaction with the page while the message box is
92527      * displayed (defaults to true)</div></li>
92528      * <li><b>msg</b> : String<div class="sub-desc">A string that will replace the existing message box body text (defaults to the
92529      * XHTML-compliant non-breaking space character '&amp;#160;')</div></li>
92530      * <li><a id="show-option-multiline"></a><b>multiline</b> : Boolean<div class="sub-desc">
92531      * True to prompt the user to enter multi-line text (defaults to false)</div></li>
92532      * <li><b>progress</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
92533      * <li><b>progressText</b> : String<div class="sub-desc">The text to display inside the progress bar if progress = true (defaults to '')</div></li>
92534      * <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>
92535      * <li><b>proxyDrag</b> : Boolean<div class="sub-desc">True to display a lightweight proxy while dragging (defaults to false)</div></li>
92536      * <li><b>title</b> : String<div class="sub-desc">The title text</div></li>
92537      * <li><b>value</b> : String<div class="sub-desc">The string value to set into the active textbox element if displayed</div></li>
92538      * <li><b>wait</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
92539      * <li><b>waitConfig</b> : Object<div class="sub-desc">A {@link Ext.ProgressBar#waitConfig} object (applies only if wait = true)</div></li>
92540      * <li><b>width</b> : Number<div class="sub-desc">The width of the dialog in pixels</div></li>
92541      * </ul>
92542      * Example usage:
92543      * <pre><code>
92544 Ext.Msg.show({
92545 title: 'Address',
92546 msg: 'Please enter your address:',
92547 width: 300,
92548 buttons: Ext.window.MessageBox.OKCANCEL,
92549 multiline: true,
92550 fn: saveAddress,
92551 animateTarget: 'addAddressBtn',
92552 icon: Ext.window.MessageBox.INFO
92553 });
92554 </code></pre>
92555      * @return {Ext.window.MessageBox} this
92556      */
92557     show: function(cfg) {
92558         var me = this;
92559
92560         me.reconfigure(cfg);
92561         me.addCls(cfg.cls);
92562         if (cfg.animateTarget) {
92563             me.doAutoSize(false);
92564             me.callParent();
92565         } else {
92566             me.callParent();
92567             me.doAutoSize(true);
92568         }
92569         return me;
92570     },
92571
92572     afterShow: function(){
92573         if (this.animateTarget) {
92574             this.center();
92575         }
92576         this.callParent(arguments);
92577     },
92578
92579     doAutoSize: function(center) {
92580         var me = this,
92581             icon = me.iconComponent,
92582             iconHeight = me.iconHeight;
92583
92584         if (!Ext.isDefined(me.frameWidth)) {
92585             me.frameWidth = me.el.getWidth() - me.body.getWidth();
92586         }
92587
92588         // reset to the original dimensions
92589         icon.setHeight(iconHeight);
92590
92591         // Allow per-invocation override of minWidth
92592         me.minWidth = me.cfg.minWidth || Ext.getClass(this).prototype.minWidth;
92593
92594         // Set best possible size based upon allowing the text to wrap in the maximized Window, and
92595         // then constraining it to within the max with. Then adding up constituent element heights.
92596         me.topContainer.doLayout();
92597         if (Ext.isIE6 || Ext.isIEQuirks) {
92598             // In IE quirks, the initial full width of the prompt fields will prevent the container element
92599             // from collapsing once sized down, so temporarily force them to a small width. They'll get
92600             // layed out to their final width later when setting the final window size.
92601             me.textField.setCalculatedSize(9);
92602             me.textArea.setCalculatedSize(9);
92603         }
92604         var width = me.cfg.width || me.msg.getWidth() + icon.getWidth() + 25, /* topContainer's layout padding */
92605             height = (me.header.rendered ? me.header.getHeight() : 0) +
92606             Math.max(me.promptContainer.getHeight(), icon.getHeight()) +
92607             me.progressBar.getHeight() +
92608             (me.bottomTb.rendered ? me.bottomTb.getHeight() : 0) + 20 ;/* topContainer's layout padding */
92609
92610         // Update to the size of the content, this way the text won't wrap under the icon.
92611         icon.setHeight(Math.max(iconHeight, me.msg.getHeight()));
92612         me.setSize(width + me.frameWidth, height + me.frameWidth);
92613         if (center) {
92614             me.center();
92615         }
92616         return me;
92617     },
92618
92619     updateText: function(text) {
92620         this.msg.update(text);
92621         return this.doAutoSize(true);
92622     },
92623
92624     /**
92625      * Adds the specified icon to the dialog.  By default, the class 'ext-mb-icon' is applied for default
92626      * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('')
92627      * to clear any existing icon. This method must be called before the MessageBox is shown.
92628      * The following built-in icon classes are supported, but you can also pass in a custom class name:
92629      * <pre>
92630 Ext.window.MessageBox.INFO
92631 Ext.window.MessageBox.WARNING
92632 Ext.window.MessageBox.QUESTION
92633 Ext.window.MessageBox.ERROR
92634      *</pre>
92635      * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon
92636      * @return {Ext.window.MessageBox} this
92637      */
92638     setIcon : function(icon) {
92639         var me = this;
92640         me.iconComponent.removeCls(me.iconCls);
92641         if (icon) {
92642             me.iconComponent.show();
92643             me.iconComponent.addCls(Ext.baseCSSPrefix + 'dlg-icon');
92644             me.iconComponent.addCls(me.iconCls = icon);
92645         } else {
92646             me.iconComponent.removeCls(Ext.baseCSSPrefix + 'dlg-icon');
92647             me.iconComponent.hide();
92648         }
92649         return me;
92650     },
92651
92652     /**
92653      * Updates a progress-style message box's text and progress bar. Only relevant on message boxes
92654      * initiated via {@link Ext.window.MessageBox#progress} or {@link Ext.window.MessageBox#wait},
92655      * or by calling {@link Ext.window.MessageBox#show} with progress: true.
92656      * @param {Number} value Any number between 0 and 1 (e.g., .5, defaults to 0)
92657      * @param {String} progressText The progress text to display inside the progress bar (defaults to '')
92658      * @param {String} msg The message box's body text is replaced with the specified string (defaults to undefined
92659      * so that any existing body text will not get overwritten by default unless a new value is passed in)
92660      * @return {Ext.window.MessageBox} this
92661      */
92662     updateProgress : function(value, progressText, msg){
92663         this.progressBar.updateProgress(value, progressText);
92664         if (msg){
92665             this.updateText(msg);
92666         }
92667         return this;
92668     },
92669
92670     onEsc: function() {
92671         if (this.closable !== false) {
92672             this.callParent(arguments);
92673         }
92674     },
92675
92676     /**
92677      * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm).
92678      * If a callback function is passed it will be called after the user clicks either button,
92679      * and the id of the button that was clicked will be passed as the only parameter to the callback
92680      * (could also be the top-right close button).
92681      * @param {String} title The title bar text
92682      * @param {String} msg The message box body text
92683      * @param {Function} fn (optional) The callback function invoked after the message box is closed
92684      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
92685      * @return {Ext.window.MessageBox} this
92686      */
92687     confirm: function(cfg, msg, fn, scope) {
92688         if (Ext.isString(cfg)) {
92689             cfg = {
92690                 title: cfg,
92691                 icon: 'ext-mb-question',
92692                 msg: msg,
92693                 buttons: this.YESNO,
92694                 callback: fn,
92695                 scope: scope
92696             };
92697         }
92698         return this.show(cfg);
92699     },
92700
92701     /**
92702      * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt).
92703      * The prompt can be a single-line or multi-line textbox.  If a callback function is passed it will be called after the user
92704      * clicks either button, and the id of the button that was clicked (could also be the top-right
92705      * close button) and the text that was entered will be passed as the two parameters to the callback.
92706      * @param {String} title The title bar text
92707      * @param {String} msg The message box body text
92708      * @param {Function} fn (optional) The callback function invoked after the message box is closed
92709      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
92710      * @param {Boolean/Number} multiline (optional) True to create a multiline textbox using the defaultTextHeight
92711      * property, or the height in pixels to create the textbox (defaults to false / single-line)
92712      * @param {String} value (optional) Default value of the text input element (defaults to '')
92713      * @return {Ext.window.MessageBox} this
92714      */
92715     prompt : function(cfg, msg, fn, scope, multiline, value){
92716         if (Ext.isString(cfg)) {
92717             cfg = {
92718                 prompt: true,
92719                 title: cfg,
92720                 minWidth: this.minPromptWidth,
92721                 msg: msg,
92722                 buttons: this.OKCANCEL,
92723                 callback: fn,
92724                 scope: scope,
92725                 multiline: multiline,
92726                 value: value
92727             };
92728         }
92729         return this.show(cfg);
92730     },
92731
92732     /**
92733      * Displays a message box with an infinitely auto-updating progress bar.  This can be used to block user
92734      * interaction while waiting for a long-running process to complete that does not have defined intervals.
92735      * You are responsible for closing the message box when the process is complete.
92736      * @param {String} msg The message box body text
92737      * @param {String} title (optional) The title bar text
92738      * @param {Object} config (optional) A {@link Ext.ProgressBar#waitConfig} object
92739      * @return {Ext.window.MessageBox} this
92740      */
92741     wait : function(cfg, title, config){
92742         if (Ext.isString(cfg)) {
92743             cfg = {
92744                 title : title,
92745                 msg : cfg,
92746                 closable: false,
92747                 wait: true,
92748                 modal: true,
92749                 minWidth: this.minProgressWidth,
92750                 waitConfig: config
92751             };
92752         }
92753         return this.show(cfg);
92754     },
92755
92756     /**
92757      * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt).
92758      * If a callback function is passed it will be called after the user clicks the button, and the
92759      * id of the button that was clicked will be passed as the only parameter to the callback
92760      * (could also be the top-right close button).
92761      * @param {String} title The title bar text
92762      * @param {String} msg The message box body text
92763      * @param {Function} fn (optional) The callback function invoked after the message box is closed
92764      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
92765      * @return {Ext.window.MessageBox} this
92766      */
92767     alert: function(cfg, msg, fn, scope) {
92768         if (Ext.isString(cfg)) {
92769             cfg = {
92770                 title : cfg,
92771                 msg : msg,
92772                 buttons: this.OK,
92773                 fn: fn,
92774                 scope : scope,
92775                 minWidth: this.minWidth
92776             };
92777         }
92778         return this.show(cfg);
92779     },
92780
92781     /**
92782      * Displays a message box with a progress bar.  This message box has no buttons and is not closeable by
92783      * the user.  You are responsible for updating the progress bar as needed via {@link Ext.window.MessageBox#updateProgress}
92784      * and closing the message box when the process is complete.
92785      * @param {String} title The title bar text
92786      * @param {String} msg The message box body text
92787      * @param {String} progressText (optional) The text to display inside the progress bar (defaults to '')
92788      * @return {Ext.window.MessageBox} this
92789      */
92790     progress : function(cfg, msg, progressText){
92791         if (Ext.isString(cfg)) {
92792             cfg = {
92793                 title: cfg,
92794                 msg: msg,
92795                 progressText: progressText
92796             };
92797         }
92798         return this.show(cfg);
92799     }
92800 }, function() {
92801     Ext.MessageBox = Ext.Msg = new this();
92802 });
92803 /**
92804  * @class Ext.form.Basic
92805  * @extends Ext.util.Observable
92806
92807 Provides input field management, validation, submission, and form loading services for the collection
92808 of {@link Ext.form.field.Field Field} instances within a {@link Ext.container.Container}. It is recommended
92809 that you use a {@link Ext.form.Panel} as the form container, as that has logic to automatically
92810 hook up an instance of {@link Ext.form.Basic} (plus other conveniences related to field configuration.)
92811
92812 #Form Actions#
92813
92814 The Basic class delegates the handling of form loads and submits to instances of {@link Ext.form.action.Action}.
92815 See the various Action implementations for specific details of each one's functionality, as well as the
92816 documentation for {@link #doAction} which details the configuration options that can be specified in
92817 each action call.
92818
92819 The default submit Action is {@link Ext.form.action.Submit}, which uses an Ajax request to submit the
92820 form's values to a configured URL. To enable normal browser submission of an Ext form, use the
92821 {@link #standardSubmit} config option.
92822
92823 Note: File uploads are not performed using normal 'Ajax' techniques; see the description for
92824 {@link #hasUpload} for details.
92825
92826 #Example usage:#
92827
92828     Ext.create('Ext.form.Panel', {
92829         title: 'Basic Form',
92830         renderTo: Ext.getBody(),
92831         bodyPadding: 5,
92832         width: 350,
92833
92834         // Any configuration items here will be automatically passed along to
92835         // the Ext.form.Basic instance when it gets created.
92836
92837         // The form will submit an AJAX request to this URL when submitted
92838         url: 'save-form.php',
92839
92840         items: [{
92841             fieldLabel: 'Field',
92842             name: 'theField'
92843         }],
92844
92845         buttons: [{
92846             text: 'Submit',
92847             handler: function() {
92848                 // The getForm() method returns the Ext.form.Basic instance:
92849                 var form = this.up('form').getForm();
92850                 if (form.isValid()) {
92851                     // Submit the Ajax request and handle the response
92852                     form.submit({
92853                         success: function(form, action) {
92854                            Ext.Msg.alert('Success', action.result.msg);
92855                         },
92856                         failure: function(form, action) {
92857                             Ext.Msg.alert('Failed', action.result.msg);
92858                         }
92859                     });
92860                 }
92861             }
92862         }]
92863     });
92864
92865  * @constructor
92866  * @param {Ext.container.Container} owner The component that is the container for the form, usually a {@link Ext.form.Panel}
92867  * @param {Object} config Configuration options. These are normally specified in the config to the
92868  * {@link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically.
92869  *
92870  * @markdown
92871  * @docauthor Jason Johnston <jason@sencha.com>
92872  */
92873
92874
92875
92876 Ext.define('Ext.form.Basic', {
92877     extend: 'Ext.util.Observable',
92878     alternateClassName: 'Ext.form.BasicForm',
92879     requires: ['Ext.util.MixedCollection', 'Ext.form.action.Load', 'Ext.form.action.Submit',
92880                'Ext.window.MessageBox', 'Ext.data.Errors', 'Ext.util.DelayedTask'],
92881
92882     constructor: function(owner, config) {
92883         var me = this,
92884             onItemAddOrRemove = me.onItemAddOrRemove;
92885
92886         /**
92887          * @property owner
92888          * @type Ext.container.Container
92889          * The container component to which this BasicForm is attached.
92890          */
92891         me.owner = owner;
92892
92893         // Listen for addition/removal of fields in the owner container
92894         me.mon(owner, {
92895             add: onItemAddOrRemove,
92896             remove: onItemAddOrRemove,
92897             scope: me
92898         });
92899
92900         Ext.apply(me, config);
92901
92902         // Normalize the paramOrder to an Array
92903         if (Ext.isString(me.paramOrder)) {
92904             me.paramOrder = me.paramOrder.split(/[\s,|]/);
92905         }
92906
92907         me.checkValidityTask = Ext.create('Ext.util.DelayedTask', me.checkValidity, me);
92908
92909         me.addEvents(
92910             /**
92911              * @event beforeaction
92912              * Fires before any action is performed. Return false to cancel the action.
92913              * @param {Ext.form.Basic} this
92914              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} to be performed
92915              */
92916             'beforeaction',
92917             /**
92918              * @event actionfailed
92919              * Fires when an action fails.
92920              * @param {Ext.form.Basic} this
92921              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that failed
92922              */
92923             'actionfailed',
92924             /**
92925              * @event actioncomplete
92926              * Fires when an action is completed.
92927              * @param {Ext.form.Basic} this
92928              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that completed
92929              */
92930             'actioncomplete',
92931             /**
92932              * @event validitychange
92933              * Fires when the validity of the entire form changes.
92934              * @param {Ext.form.Basic} this
92935              * @param {Boolean} valid <tt>true</tt> if the form is now valid, <tt>false</tt> if it is now invalid.
92936              */
92937             'validitychange',
92938             /**
92939              * @event dirtychange
92940              * Fires when the dirty state of the entire form changes.
92941              * @param {Ext.form.Basic} this
92942              * @param {Boolean} dirty <tt>true</tt> if the form is now dirty, <tt>false</tt> if it is no longer dirty.
92943              */
92944             'dirtychange'
92945         );
92946         me.callParent();
92947     },
92948
92949     /**
92950      * Do any post constructor initialization
92951      * @private
92952      */
92953     initialize: function(){
92954         this.initialized = true;
92955         this.onValidityChange(!this.hasInvalidField());
92956     },
92957
92958     /**
92959      * @cfg {String} method
92960      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
92961      */
92962     /**
92963      * @cfg {Ext.data.reader.Reader} reader
92964      * An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to read
92965      * data when executing 'load' actions. This is optional as there is built-in
92966      * support for processing JSON responses.
92967      */
92968     /**
92969      * @cfg {Ext.data.reader.Reader} errorReader
92970      * <p>An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to
92971      * read field error messages returned from 'submit' actions. This is optional
92972      * as there is built-in support for processing JSON responses.</p>
92973      * <p>The Records which provide messages for the invalid Fields must use the
92974      * Field name (or id) as the Record ID, and must contain a field called 'msg'
92975      * which contains the error message.</p>
92976      * <p>The errorReader does not have to be a full-blown implementation of a
92977      * Reader. It simply needs to implement a <tt>read(xhr)</tt> function
92978      * which returns an Array of Records in an object with the following
92979      * structure:</p><pre><code>
92980 {
92981     records: recordArray
92982 }
92983 </code></pre>
92984      */
92985
92986     /**
92987      * @cfg {String} url
92988      * The URL to use for form actions if one isn't supplied in the
92989      * {@link #doAction doAction} options.
92990      */
92991
92992     /**
92993      * @cfg {Object} baseParams
92994      * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
92995      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p>
92996      */
92997
92998     /**
92999      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
93000      */
93001     timeout: 30,
93002
93003     /**
93004      * @cfg {Object} api (Optional) If specified, load and submit actions will be handled
93005      * with {@link Ext.form.action.DirectLoad} and {@link Ext.form.action.DirectLoad}.
93006      * Methods which have been imported by {@link Ext.direct.Manager} can be specified here to load and submit
93007      * forms.
93008      * Such as the following:<pre><code>
93009 api: {
93010     load: App.ss.MyProfile.load,
93011     submit: App.ss.MyProfile.submit
93012 }
93013 </code></pre>
93014      * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
93015      * to customize how the load method is invoked.
93016      * Submit actions will always use a standard form submit. The <tt>formHandler</tt> configuration must
93017      * be set on the associated server-side method which has been imported by {@link Ext.direct.Manager}.</p>
93018      */
93019
93020     /**
93021      * @cfg {Array/String} paramOrder <p>A list of params to be executed server side.
93022      * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
93023      * <code>load</code> configuration.</p>
93024      * <p>Specify the params in the order in which they must be executed on the
93025      * server-side as either (1) an Array of String values, or (2) a String of params
93026      * delimited by either whitespace, comma, or pipe. For example,
93027      * any of the following would be acceptable:</p><pre><code>
93028 paramOrder: ['param1','param2','param3']
93029 paramOrder: 'param1 param2 param3'
93030 paramOrder: 'param1,param2,param3'
93031 paramOrder: 'param1|param2|param'
93032      </code></pre>
93033      */
93034
93035     /**
93036      * @cfg {Boolean} paramsAsHash Only used for the <code>{@link #api}</code>
93037      * <code>load</code> configuration. If <tt>true</tt>, parameters will be sent as a
93038      * single hash collection of named arguments (defaults to <tt>false</tt>). Providing a
93039      * <tt>{@link #paramOrder}</tt> nullifies this configuration.
93040      */
93041     paramsAsHash: false,
93042
93043     /**
93044      * @cfg {String} waitTitle
93045      * The default title to show for the waiting message box (defaults to <tt>'Please Wait...'</tt>)
93046      */
93047     waitTitle: 'Please Wait...',
93048
93049     /**
93050      * @cfg {Boolean} trackResetOnLoad If set to <tt>true</tt>, {@link #reset}() resets to the last loaded
93051      * or {@link #setValues}() data instead of when the form was first created.  Defaults to <tt>false</tt>.
93052      */
93053     trackResetOnLoad: false,
93054
93055     /**
93056      * @cfg {Boolean} standardSubmit
93057      * <p>If set to <tt>true</tt>, a standard HTML form submit is used instead
93058      * of a XHR (Ajax) style form submission. Defaults to <tt>false</tt>. All of
93059      * the field values, plus any additional params configured via {@link #baseParams}
93060      * and/or the <code>options</code> to {@link #submit}, will be included in the
93061      * values submitted in the form.</p>
93062      */
93063
93064     /**
93065      * @cfg {Mixed} waitMsgTarget
93066      * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
93067      * element by passing it or its id or mask the form itself by passing in true. Defaults to <tt>undefined</tt>.
93068      */
93069
93070
93071     // Private
93072     wasDirty: false,
93073
93074
93075     /**
93076      * Destroys this object.
93077      */
93078     destroy: function() {
93079         this.clearListeners();
93080         this.checkValidityTask.cancel();
93081     },
93082
93083     /**
93084      * @private
93085      * Handle addition or removal of descendant items. Invalidates the cached list of fields
93086      * so that {@link #getFields} will do a fresh query next time it is called. Also adds listeners
93087      * for state change events on added fields, and tracks components with formBind=true.
93088      */
93089     onItemAddOrRemove: function(parent, child) {
93090         var me = this,
93091             isAdding = !!child.ownerCt,
93092             isContainer = child.isContainer;
93093
93094         function handleField(field) {
93095             // Listen for state change events on fields
93096             me[isAdding ? 'mon' : 'mun'](field, {
93097                 validitychange: me.checkValidity,
93098                 dirtychange: me.checkDirty,
93099                 scope: me,
93100                 buffer: 100 //batch up sequential calls to avoid excessive full-form validation
93101             });
93102             // Flush the cached list of fields
93103             delete me._fields;
93104         }
93105
93106         if (child.isFormField) {
93107             handleField(child);
93108         }
93109         else if (isContainer) {
93110             // Walk down
93111             Ext.Array.forEach(child.query('[isFormField]'), handleField);
93112         }
93113
93114         // Flush the cached list of formBind components
93115         delete this._boundItems;
93116
93117         // Check form bind, but only after initial add. Batch it to prevent excessive validation
93118         // calls when many fields are being added at once.
93119         if (me.initialized) {
93120             me.checkValidityTask.delay(10);
93121         }
93122     },
93123
93124     /**
93125      * Return all the {@link Ext.form.field.Field} components in the owner container.
93126      * @return {Ext.util.MixedCollection} Collection of the Field objects
93127      */
93128     getFields: function() {
93129         var fields = this._fields;
93130         if (!fields) {
93131             fields = this._fields = Ext.create('Ext.util.MixedCollection');
93132             fields.addAll(this.owner.query('[isFormField]'));
93133         }
93134         return fields;
93135     },
93136
93137     getBoundItems: function() {
93138         var boundItems = this._boundItems;
93139         if (!boundItems) {
93140             boundItems = this._boundItems = Ext.create('Ext.util.MixedCollection');
93141             boundItems.addAll(this.owner.query('[formBind]'));
93142         }
93143         return boundItems;
93144     },
93145
93146     /**
93147      * Returns true if the form contains any invalid fields. No fields will be marked as invalid
93148      * as a result of calling this; to trigger marking of fields use {@link #isValid} instead.
93149      */
93150     hasInvalidField: function() {
93151         return !!this.getFields().findBy(function(field) {
93152             var preventMark = field.preventMark,
93153                 isValid;
93154             field.preventMark = true;
93155             isValid = field.isValid();
93156             field.preventMark = preventMark;
93157             return !isValid;
93158         });
93159     },
93160
93161     /**
93162      * Returns true if client-side validation on the form is successful. Any invalid fields will be
93163      * marked as invalid. If you only want to determine overall form validity without marking anything,
93164      * use {@link #hasInvalidField} instead.
93165      * @return Boolean
93166      */
93167     isValid: function() {
93168         var me = this,
93169             invalid;
93170         me.batchLayouts(function() {
93171             invalid = me.getFields().filterBy(function(field) {
93172                 return !field.validate();
93173             });
93174         });
93175         return invalid.length < 1;
93176     },
93177
93178     /**
93179      * Check whether the validity of the entire form has changed since it was last checked, and
93180      * if so fire the {@link #validitychange validitychange} event. This is automatically invoked
93181      * when an individual field's validity changes.
93182      */
93183     checkValidity: function() {
93184         var me = this,
93185             valid = !me.hasInvalidField();
93186         if (valid !== me.wasValid) {
93187             me.onValidityChange(valid);
93188             me.fireEvent('validitychange', me, valid);
93189             me.wasValid = valid;
93190         }
93191     },
93192
93193     /**
93194      * @private
93195      * Handle changes in the form's validity. If there are any sub components with
93196      * formBind=true then they are enabled/disabled based on the new validity.
93197      * @param {Boolean} valid
93198      */
93199     onValidityChange: function(valid) {
93200         var boundItems = this.getBoundItems();
93201         if (boundItems) {
93202             boundItems.each(function(cmp) {
93203                 if (cmp.disabled === valid) {
93204                     cmp.setDisabled(!valid);
93205                 }
93206             });
93207         }
93208     },
93209
93210     /**
93211      * <p>Returns true if any fields in this form have changed from their original values.</p>
93212      * <p>Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
93213      * Fields' <em>original values</em> are updated when the values are loaded by {@link #setValues}
93214      * or {@link #loadRecord}.</p>
93215      * @return Boolean
93216      */
93217     isDirty: function() {
93218         return !!this.getFields().findBy(function(f) {
93219             return f.isDirty();
93220         });
93221     },
93222
93223     /**
93224      * Check whether the dirty state of the entire form has changed since it was last checked, and
93225      * if so fire the {@link #dirtychange dirtychange} event. This is automatically invoked
93226      * when an individual field's dirty state changes.
93227      */
93228     checkDirty: function() {
93229         var dirty = this.isDirty();
93230         if (dirty !== this.wasDirty) {
93231             this.fireEvent('dirtychange', this, dirty);
93232             this.wasDirty = dirty;
93233         }
93234     },
93235
93236     /**
93237      * <p>Returns true if the form contains a file upload field. This is used to determine the
93238      * method for submitting the form: File uploads are not performed using normal 'Ajax' techniques,
93239      * that is they are <b>not</b> performed using XMLHttpRequests. Instead a hidden <tt>&lt;form></tt>
93240      * element containing all the fields is created temporarily and submitted with its
93241      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
93242      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
93243      * but removed after the return data has been gathered.</p>
93244      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
93245      * server is using JSON to send the return object, then the
93246      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
93247      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
93248      * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
93249      * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
93250      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
93251      * is created containing a <tt>responseText</tt> property in order to conform to the
93252      * requirements of event handlers and callbacks.</p>
93253      * <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>
93254      * and some server technologies (notably JEE) may require some custom processing in order to
93255      * retrieve parameter names and parameter values from the packet content.</p>
93256      * @return Boolean
93257      */
93258     hasUpload: function() {
93259         return !!this.getFields().findBy(function(f) {
93260             return f.isFileUpload();
93261         });
93262     },
93263
93264     /**
93265      * Performs a predefined action (an implementation of {@link Ext.form.action.Action})
93266      * to perform application-specific processing.
93267      * @param {String/Ext.form.action.Action} action The name of the predefined action type,
93268      * or instance of {@link Ext.form.action.Action} to perform.
93269      * @param {Object} options (optional) The options to pass to the {@link Ext.form.action.Action}
93270      * that will get created, if the <tt>action</tt> argument is a String.
93271      * <p>All of the config options listed below are supported by both the
93272      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
93273      * actions unless otherwise noted (custom actions could also accept
93274      * other config options):</p><ul>
93275      *
93276      * <li><b>url</b> : String<div class="sub-desc">The url for the action (defaults
93277      * to the form's {@link #url}.)</div></li>
93278      *
93279      * <li><b>method</b> : String<div class="sub-desc">The form method to use (defaults
93280      * to the form's method, or POST if not defined)</div></li>
93281      *
93282      * <li><b>params</b> : String/Object<div class="sub-desc"><p>The params to pass
93283      * (defaults to the form's baseParams, or none if not defined)</p>
93284      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p></div></li>
93285      *
93286      * <li><b>headers</b> : Object<div class="sub-desc">Request headers to set for the action.</div></li>
93287      *
93288      * <li><b>success</b> : Function<div class="sub-desc">The callback that will
93289      * be invoked after a successful response (see top of
93290      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
93291      * for a description of what constitutes a successful response).
93292      * The function is passed the following parameters:<ul>
93293      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
93294      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
93295      * <div class="sub-desc">The action object contains these properties of interest:<ul>
93296      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
93297      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
93298      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
93299      * </ul></div></li></ul></div></li>
93300      *
93301      * <li><b>failure</b> : Function<div class="sub-desc">The callback that will be invoked after a
93302      * failed transaction attempt. The function is passed the following parameters:<ul>
93303      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
93304      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
93305      * <div class="sub-desc">The action object contains these properties of interest:<ul>
93306      * <li><tt>{@link Ext.form.action.Action#failureType failureType}</tt></li>
93307      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
93308      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
93309      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
93310      * </ul></div></li></ul></div></li>
93311      *
93312      * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the
93313      * callback functions (The <tt>this</tt> reference for the callback functions).</div></li>
93314      *
93315      * <li><b>clientValidation</b> : Boolean<div class="sub-desc">Submit Action only.
93316      * Determines whether a Form's fields are validated in a final call to
93317      * {@link Ext.form.Basic#isValid isValid} prior to submission. Set to <tt>false</tt>
93318      * to prevent this. If undefined, pre-submission field validation is performed.</div></li></ul>
93319      *
93320      * @return {Ext.form.Basic} this
93321      */
93322     doAction: function(action, options) {
93323         if (Ext.isString(action)) {
93324             action = Ext.ClassManager.instantiateByAlias('formaction.' + action, Ext.apply({}, options, {form: this}));
93325         }
93326         if (this.fireEvent('beforeaction', this, action) !== false) {
93327             this.beforeAction(action);
93328             Ext.defer(action.run, 100, action);
93329         }
93330         return this;
93331     },
93332
93333     /**
93334      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Submit submit action}. This will use the
93335      * {@link Ext.form.action.Submit AJAX submit action} by default. If the {@link #standardsubmit} config is
93336      * enabled it will use a standard form element to submit, or if the {@link #api} config is present it will
93337      * use the {@link Ext.form.action.DirectLoad Ext.direct.Direct submit action}.
93338      * @param {Object} options The options to pass to the action (see {@link #doAction} for details).<br>
93339      * <p>The following code:</p><pre><code>
93340 myFormPanel.getForm().submit({
93341     clientValidation: true,
93342     url: 'updateConsignment.php',
93343     params: {
93344         newStatus: 'delivered'
93345     },
93346     success: function(form, action) {
93347        Ext.Msg.alert('Success', action.result.msg);
93348     },
93349     failure: function(form, action) {
93350         switch (action.failureType) {
93351             case Ext.form.action.Action.CLIENT_INVALID:
93352                 Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
93353                 break;
93354             case Ext.form.action.Action.CONNECT_FAILURE:
93355                 Ext.Msg.alert('Failure', 'Ajax communication failed');
93356                 break;
93357             case Ext.form.action.Action.SERVER_INVALID:
93358                Ext.Msg.alert('Failure', action.result.msg);
93359        }
93360     }
93361 });
93362 </code></pre>
93363      * would process the following server response for a successful submission:<pre><code>
93364 {
93365     "success":true, // note this is Boolean, not string
93366     "msg":"Consignment updated"
93367 }
93368 </code></pre>
93369      * and the following server response for a failed submission:<pre><code>
93370 {
93371     "success":false, // note this is Boolean, not string
93372     "msg":"You do not have permission to perform this operation"
93373 }
93374 </code></pre>
93375      * @return {Ext.form.Basic} this
93376      */
93377     submit: function(options) {
93378         return this.doAction(this.standardSubmit ? 'standardsubmit' : this.api ? 'directsubmit' : 'submit', options);
93379     },
93380
93381     /**
93382      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Load load action}.
93383      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
93384      * @return {Ext.form.Basic} this
93385      */
93386     load: function(options) {
93387         return this.doAction(this.api ? 'directload' : 'load', options);
93388     },
93389
93390     /**
93391      * Persists the values in this form into the passed {@link Ext.data.Model} object in a beginEdit/endEdit block.
93392      * @param {Ext.data.Record} record The record to edit
93393      * @return {Ext.form.Basic} this
93394      */
93395     updateRecord: function(record) {
93396         var fields = record.fields,
93397             values = this.getFieldValues(),
93398             name,
93399             obj = {};
93400
93401         fields.each(function(f) {
93402             name = f.name;
93403             if (name in values) {
93404                 obj[name] = values[name];
93405             }
93406         });
93407
93408         record.beginEdit();
93409         record.set(obj);
93410         record.endEdit();
93411
93412         return this;
93413     },
93414
93415     /**
93416      * Loads an {@link Ext.data.Model} into this form by calling {@link #setValues} with the
93417      * {@link Ext.data.Model#data record data}.
93418      * See also {@link #trackResetOnLoad}.
93419      * @param {Ext.data.Model} record The record to load
93420      * @return {Ext.form.Basic} this
93421      */
93422     loadRecord: function(record) {
93423         this._record = record;
93424         return this.setValues(record.data);
93425     },
93426     
93427     /**
93428      * Returns the last Ext.data.Model instance that was loaded via {@link #loadRecord}
93429      * @return {Ext.data.Model} The record
93430      */
93431     getRecord: function() {
93432         return this._record;
93433     },
93434
93435     /**
93436      * @private
93437      * Called before an action is performed via {@link #doAction}.
93438      * @param {Ext.form.action.Action} action The Action instance that was invoked
93439      */
93440     beforeAction: function(action) {
93441         var waitMsg = action.waitMsg,
93442             maskCls = Ext.baseCSSPrefix + 'mask-loading',
93443             waitMsgTarget;
93444
93445         // Call HtmlEditor's syncValue before actions
93446         this.getFields().each(function(f) {
93447             if (f.isFormField && f.syncValue) {
93448                 f.syncValue();
93449             }
93450         });
93451
93452         if (waitMsg) {
93453             waitMsgTarget = this.waitMsgTarget;
93454             if (waitMsgTarget === true) {
93455                 this.owner.el.mask(waitMsg, maskCls);
93456             } else if (waitMsgTarget) {
93457                 waitMsgTarget = this.waitMsgTarget = Ext.get(waitMsgTarget);
93458                 waitMsgTarget.mask(waitMsg, maskCls);
93459             } else {
93460                 Ext.MessageBox.wait(waitMsg, action.waitTitle || this.waitTitle);
93461             }
93462         }
93463     },
93464
93465     /**
93466      * @private
93467      * Called after an action is performed via {@link #doAction}.
93468      * @param {Ext.form.action.Action} action The Action instance that was invoked
93469      * @param {Boolean} success True if the action completed successfully, false, otherwise.
93470      */
93471     afterAction: function(action, success) {
93472         if (action.waitMsg) {
93473             var MessageBox = Ext.MessageBox,
93474                 waitMsgTarget = this.waitMsgTarget;
93475             if (waitMsgTarget === true) {
93476                 this.owner.el.unmask();
93477             } else if (waitMsgTarget) {
93478                 waitMsgTarget.unmask();
93479             } else {
93480                 MessageBox.updateProgress(1);
93481                 MessageBox.hide();
93482             }
93483         }
93484         if (success) {
93485             if (action.reset) {
93486                 this.reset();
93487             }
93488             Ext.callback(action.success, action.scope || action, [this, action]);
93489             this.fireEvent('actioncomplete', this, action);
93490         } else {
93491             Ext.callback(action.failure, action.scope || action, [this, action]);
93492             this.fireEvent('actionfailed', this, action);
93493         }
93494     },
93495
93496
93497     /**
93498      * Find a specific {@link Ext.form.field.Field} in this form by id or name.
93499      * @param {String} id The value to search for (specify either a {@link Ext.Component#id id} or
93500      * {@link Ext.form.field.Field#getName name or hiddenName}).
93501      * @return Ext.form.field.Field The first matching field, or <tt>null</tt> if none was found.
93502      */
93503     findField: function(id) {
93504         return this.getFields().findBy(function(f) {
93505             return f.id === id || f.getName() === id;
93506         });
93507     },
93508
93509
93510     /**
93511      * Mark fields in this form invalid in bulk.
93512      * @param {Array/Object} errors Either an array in the form <code>[{id:'fieldId', msg:'The message'}, ...]</code>,
93513      * an object hash of <code>{id: msg, id2: msg2}</code>, or a {@link Ext.data.Errors} object.
93514      * @return {Ext.form.Basic} this
93515      */
93516     markInvalid: function(errors) {
93517         var me = this;
93518
93519         function mark(fieldId, msg) {
93520             var field = me.findField(fieldId);
93521             if (field) {
93522                 field.markInvalid(msg);
93523             }
93524         }
93525
93526         if (Ext.isArray(errors)) {
93527             Ext.each(errors, function(err) {
93528                 mark(err.id, err.msg);
93529             });
93530         }
93531         else if (errors instanceof Ext.data.Errors) {
93532             errors.each(function(err) {
93533                 mark(err.field, err.message);
93534             });
93535         }
93536         else {
93537             Ext.iterate(errors, mark);
93538         }
93539         return this;
93540     },
93541
93542     /**
93543      * Set values for fields in this form in bulk.
93544      * @param {Array/Object} values Either an array in the form:<pre><code>
93545 [{id:'clientName', value:'Fred. Olsen Lines'},
93546  {id:'portOfLoading', value:'FXT'},
93547  {id:'portOfDischarge', value:'OSL'} ]</code></pre>
93548      * or an object hash of the form:<pre><code>
93549 {
93550     clientName: 'Fred. Olsen Lines',
93551     portOfLoading: 'FXT',
93552     portOfDischarge: 'OSL'
93553 }</code></pre>
93554      * @return {Ext.form.Basic} this
93555      */
93556     setValues: function(values) {
93557         var me = this;
93558
93559         function setVal(fieldId, val) {
93560             var field = me.findField(fieldId);
93561             if (field) {
93562                 field.setValue(val);
93563                 if (me.trackResetOnLoad) {
93564                     field.resetOriginalValue();
93565                 }
93566             }
93567         }
93568
93569         if (Ext.isArray(values)) {
93570             // array of objects
93571             Ext.each(values, function(val) {
93572                 setVal(val.id, val.value);
93573             });
93574         } else {
93575             // object hash
93576             Ext.iterate(values, setVal);
93577         }
93578         return this;
93579     },
93580
93581     /**
93582      * Retrieves the fields in the form as a set of key/value pairs, using their
93583      * {@link Ext.form.field.Field#getSubmitData getSubmitData()} method to collect the values.
93584      * If multiple fields return values under the same name those values will be combined into an Array.
93585      * This is similar to {@link #getFieldValues} except that this method collects only String values for
93586      * submission, while getFieldValues collects type-specific data values (e.g. Date objects for date fields.)
93587      * @param {Boolean} asString (optional) If true, will return the key/value collection as a single
93588      * URL-encoded param string. Defaults to false.
93589      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
93590      * Defaults to false.
93591      * @param {Boolean} includeEmptyText (optional) If true, the configured emptyText of empty fields will be used.
93592      * Defaults to false.
93593      * @return {String/Object}
93594      */
93595     getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
93596         var values = {};
93597
93598         this.getFields().each(function(field) {
93599             if (!dirtyOnly || field.isDirty()) {
93600                 var data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);
93601                 if (Ext.isObject(data)) {
93602                     Ext.iterate(data, function(name, val) {
93603                         if (includeEmptyText && val === '') {
93604                             val = field.emptyText || '';
93605                         }
93606                         if (name in values) {
93607                             var bucket = values[name],
93608                                 isArray = Ext.isArray;
93609                             if (!isArray(bucket)) {
93610                                 bucket = values[name] = [bucket];
93611                             }
93612                             if (isArray(val)) {
93613                                 values[name] = bucket.concat(val);
93614                             } else {
93615                                 bucket.push(val);
93616                             }
93617                         } else {
93618                             values[name] = val;
93619                         }
93620                     });
93621                 }
93622             }
93623         });
93624
93625         if (asString) {
93626             values = Ext.Object.toQueryString(values);
93627         }
93628         return values;
93629     },
93630
93631     /**
93632      * Retrieves the fields in the form as a set of key/value pairs, using their
93633      * {@link Ext.form.field.Field#getModelData getModelData()} method to collect the values.
93634      * If multiple fields return values under the same name those values will be combined into an Array.
93635      * This is similar to {@link #getValues} except that this method collects type-specific data values
93636      * (e.g. Date objects for date fields) while getValues returns only String values for submission.
93637      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
93638      * Defaults to false.
93639      * @return {Object}
93640      */
93641     getFieldValues: function(dirtyOnly) {
93642         return this.getValues(false, dirtyOnly, false, true);
93643     },
93644
93645     /**
93646      * Clears all invalid field messages in this form.
93647      * @return {Ext.form.Basic} this
93648      */
93649     clearInvalid: function() {
93650         var me = this;
93651         me.batchLayouts(function() {
93652             me.getFields().each(function(f) {
93653                 f.clearInvalid();
93654             });
93655         });
93656         return me;
93657     },
93658
93659     /**
93660      * Resets all fields in this form.
93661      * @return {Ext.form.Basic} this
93662      */
93663     reset: function() {
93664         var me = this;
93665         me.batchLayouts(function() {
93666             me.getFields().each(function(f) {
93667                 f.reset();
93668             });
93669         });
93670         return me;
93671     },
93672
93673     /**
93674      * Calls {@link Ext#apply Ext.apply} for all fields in this form with the passed object.
93675      * @param {Object} obj The object to be applied
93676      * @return {Ext.form.Basic} this
93677      */
93678     applyToFields: function(obj) {
93679         this.getFields().each(function(f) {
93680             Ext.apply(f, obj);
93681         });
93682         return this;
93683     },
93684
93685     /**
93686      * Calls {@link Ext#applyIf Ext.applyIf} for all field in this form with the passed object.
93687      * @param {Object} obj The object to be applied
93688      * @return {Ext.form.Basic} this
93689      */
93690     applyIfToFields: function(obj) {
93691         this.getFields().each(function(f) {
93692             Ext.applyIf(f, obj);
93693         });
93694         return this;
93695     },
93696
93697     /**
93698      * @private
93699      * Utility wrapper that suspends layouts of all field parent containers for the duration of a given
93700      * function. Used during full-form validation and resets to prevent huge numbers of layouts.
93701      * @param {Function} fn
93702      */
93703     batchLayouts: function(fn) {
93704         var me = this,
93705             suspended = new Ext.util.HashMap();
93706
93707         // Temporarily suspend layout on each field's immediate owner so we don't get a huge layout cascade
93708         me.getFields().each(function(field) {
93709             var ownerCt = field.ownerCt;
93710             if (!suspended.contains(ownerCt)) {
93711                 suspended.add(ownerCt);
93712                 ownerCt.oldSuspendLayout = ownerCt.suspendLayout;
93713                 ownerCt.suspendLayout = true;
93714             }
93715         });
93716
93717         // Invoke the function
93718         fn();
93719
93720         // Un-suspend the container layouts
93721         suspended.each(function(id, ct) {
93722             ct.suspendLayout = ct.oldSuspendLayout;
93723             delete ct.oldSuspendLayout;
93724         });
93725
93726         // Trigger a single layout
93727         me.owner.doComponentLayout();
93728     }
93729 });
93730
93731 /**
93732  * @class Ext.form.FieldAncestor
93733
93734 A mixin for {@link Ext.container.Container} components that are likely to have form fields in their
93735 items subtree. Adds the following capabilities:
93736
93737 - Methods for handling the addition and removal of {@link Ext.form.Labelable} and {@link Ext.form.field.Field}
93738   instances at any depth within the container.
93739 - Events ({@link #fieldvaliditychange} and {@link #fielderrorchange}) for handling changes to the state
93740   of individual fields at the container level.
93741 - Automatic application of {@link #fieldDefaults} config properties to each field added within the
93742   container, to facilitate uniform configuration of all fields.
93743
93744 This mixin is primarily for internal use by {@link Ext.form.Panel} and {@link Ext.form.FieldContainer},
93745 and should not normally need to be used directly.
93746
93747  * @markdown
93748  * @docauthor Jason Johnston <jason@sencha.com>
93749  */
93750 Ext.define('Ext.form.FieldAncestor', {
93751
93752     /**
93753      * @cfg {Object} fieldDefaults
93754      * <p>If specified, the properties in this object are used as default config values for each
93755      * {@link Ext.form.Labelable} instance (e.g. {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer})
93756      * that is added as a descendant of this container. Corresponding values specified in an individual field's
93757      * own configuration, or from the {@link Ext.container.Container#defaults defaults config} of its parent container,
93758      * will take precedence. See the documentation for {@link Ext.form.Labelable} to see what config
93759      * options may be specified in the <tt>fieldDefaults</tt>.</p>
93760      * <p>Example:</p>
93761      * <pre><code>new Ext.form.Panel({
93762     fieldDefaults: {
93763         labelAlign: 'left',
93764         labelWidth: 100
93765     },
93766     items: [{
93767         xtype: 'fieldset',
93768         defaults: {
93769             labelAlign: 'top'
93770         },
93771         items: [{
93772             name: 'field1'
93773         }, {
93774             name: 'field2'
93775         }]
93776     }, {
93777         xtype: 'fieldset',
93778         items: [{
93779             name: 'field3',
93780             labelWidth: 150
93781         }, {
93782             name: 'field4'
93783         }]
93784     }]
93785 });</code></pre>
93786      * <p>In this example, field1 and field2 will get labelAlign:'top' (from the fieldset's <tt>defaults</tt>)
93787      * and labelWidth:100 (from <tt>fieldDefaults</tt>), field3 and field4 will both get labelAlign:'left' (from
93788      * <tt>fieldDefaults</tt> and field3 will use the labelWidth:150 from its own config.</p>
93789      */
93790
93791
93792     /**
93793      * @protected Initializes the FieldAncestor's state; this must be called from the initComponent method
93794      * of any components importing this mixin.
93795      */
93796     initFieldAncestor: function() {
93797         var me = this,
93798             onSubtreeChange = me.onFieldAncestorSubtreeChange;
93799
93800         me.addEvents(
93801             /**
93802              * @event fielderrorchange
93803              * Fires when the validity state of any one of the {@link Ext.form.field.Field} instances within this
93804              * container changes.
93805              * @param {Ext.form.FieldAncestor} this
93806              * @param {Ext.form.Labelable} The Field instance whose validity changed
93807              * @param {String} isValid The field's new validity state
93808              */
93809             'fieldvaliditychange',
93810
93811             /**
93812              * @event fielderrorchange
93813              * Fires when the active error message is changed for any one of the {@link Ext.form.Labelable}
93814              * instances within this container.
93815              * @param {Ext.form.FieldAncestor} this
93816              * @param {Ext.form.Labelable} The Labelable instance whose active error was changed
93817              * @param {String} error The active error message
93818              */
93819             'fielderrorchange'
93820         );
93821
93822         // Catch addition and removal of descendant fields
93823         me.on('add', onSubtreeChange, me);
93824         me.on('remove', onSubtreeChange, me);
93825
93826         me.initFieldDefaults();
93827     },
93828
93829     /**
93830      * @private Initialize the {@link #fieldDefaults} object
93831      */
93832     initFieldDefaults: function() {
93833         if (!this.fieldDefaults) {
93834             this.fieldDefaults = {};
93835         }
93836     },
93837
93838     /**
93839      * @private
93840      * Handle the addition and removal of components in the FieldAncestor component's child tree.
93841      */
93842     onFieldAncestorSubtreeChange: function(parent, child) {
93843         var me = this,
93844             isAdding = !!child.ownerCt;
93845
93846         function handleCmp(cmp) {
93847             var isLabelable = cmp.isFieldLabelable,
93848                 isField = cmp.isFormField;
93849             if (isLabelable || isField) {
93850                 if (isLabelable) {
93851                     me['onLabelable' + (isAdding ? 'Added' : 'Removed')](cmp);
93852                 }
93853                 if (isField) {
93854                     me['onField' + (isAdding ? 'Added' : 'Removed')](cmp);
93855                 }
93856             }
93857             else if (cmp.isContainer) {
93858                 Ext.Array.forEach(cmp.getRefItems(), handleCmp);
93859             }
93860         }
93861         handleCmp(child);
93862     },
93863
93864     /**
93865      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
93866      * @param {Ext.form.Labelable} labelable The instance that was added
93867      */
93868     onLabelableAdded: function(labelable) {
93869         var me = this;
93870
93871         // buffer slightly to avoid excessive firing while sub-fields are changing en masse
93872         me.mon(labelable, 'errorchange', me.handleFieldErrorChange, me, {buffer: 10});
93873
93874         labelable.setFieldDefaults(me.fieldDefaults);
93875     },
93876
93877     /**
93878      * @protected Called when a {@link Ext.form.field.Field} instance is added to the container's subtree.
93879      * @param {Ext.form.field.Field} field The field which was added
93880      */
93881     onFieldAdded: function(field) {
93882         var me = this;
93883         me.mon(field, 'validitychange', me.handleFieldValidityChange, me);
93884     },
93885
93886     /**
93887      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
93888      * @param {Ext.form.Labelable} labelable The instance that was removed
93889      */
93890     onLabelableRemoved: function(labelable) {
93891         var me = this;
93892         me.mun(labelable, 'errorchange', me.handleFieldErrorChange, me);
93893     },
93894
93895     /**
93896      * @protected Called when a {@link Ext.form.field.Field} instance is removed from the container's subtree.
93897      * @param {Ext.form.field.Field} field The field which was removed
93898      */
93899     onFieldRemoved: function(field) {
93900         var me = this;
93901         me.mun(field, 'validitychange', me.handleFieldValidityChange, me);
93902     },
93903
93904     /**
93905      * @private Handle validitychange events on sub-fields; invoke the aggregated event and method
93906      */
93907     handleFieldValidityChange: function(field, isValid) {
93908         var me = this;
93909         me.fireEvent('fieldvaliditychange', me, field, isValid);
93910         me.onFieldValidityChange();
93911     },
93912
93913     /**
93914      * @private Handle errorchange events on sub-fields; invoke the aggregated event and method
93915      */
93916     handleFieldErrorChange: function(labelable, activeError) {
93917         var me = this;
93918         me.fireEvent('fielderrorchange', me, labelable, activeError);
93919         me.onFieldErrorChange();
93920     },
93921
93922     /**
93923      * @protected Fired when the validity of any field within the container changes.
93924      * @param {Ext.form.field.Field} The sub-field whose validity changed
93925      * @param {String} The new validity state
93926      */
93927     onFieldValidityChange: Ext.emptyFn,
93928
93929     /**
93930      * @protected Fired when the error message of any field within the container changes.
93931      * @param {Ext.form.Labelable} The sub-field whose active error changed
93932      * @param {String} The new active error message
93933      */
93934     onFieldErrorChange: Ext.emptyFn
93935
93936 });
93937 /**
93938  * @class Ext.layout.container.CheckboxGroup
93939  * @extends Ext.layout.container.Container
93940  * <p>This layout implements the column arrangement for {@link Ext.form.CheckboxGroup} and {@link Ext.form.RadioGroup}.
93941  * It groups the component's sub-items into columns based on the component's
93942  * {@link Ext.form.CheckboxGroup#columns columns} and {@link Ext.form.CheckboxGroup#vertical} config properties.</p>
93943  *
93944  */
93945 Ext.define('Ext.layout.container.CheckboxGroup', {
93946     extend: 'Ext.layout.container.Container',
93947     alias: ['layout.checkboxgroup'],
93948
93949
93950     onLayout: function() {
93951         var numCols = this.getColCount(),
93952             shadowCt = this.getShadowCt(),
93953             owner = this.owner,
93954             items = owner.items,
93955             shadowItems = shadowCt.items,
93956             numItems = items.length,
93957             colIndex = 0,
93958             i, numRows;
93959
93960         // Distribute the items into the appropriate column containers. We add directly to the
93961         // containers' items collection rather than calling container.add(), because we need the
93962         // checkboxes to maintain their original ownerCt. The distribution is done on each layout
93963         // in case items have been added, removed, or reordered.
93964
93965         shadowItems.each(function(col) {
93966             col.items.clear();
93967         });
93968
93969         // If columns="auto", then the number of required columns may change as checkboxes are added/removed
93970         // from the CheckboxGroup; adjust to match.
93971         while (shadowItems.length > numCols) {
93972             shadowCt.remove(shadowItems.last());
93973         }
93974         while (shadowItems.length < numCols) {
93975             shadowCt.add({
93976                 xtype: 'container',
93977                 cls: owner.groupCls,
93978                 flex: 1
93979             });
93980         }
93981
93982         if (owner.vertical) {
93983             numRows = Math.ceil(numItems / numCols);
93984             for (i = 0; i < numItems; i++) {
93985                 if (i > 0 && i % numRows === 0) {
93986                     colIndex++;
93987                 }
93988                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
93989             }
93990         } else {
93991             for (i = 0; i < numItems; i++) {
93992                 colIndex = i % numCols;
93993                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
93994             }
93995         }
93996
93997         if (!shadowCt.rendered) {
93998             shadowCt.render(this.getRenderTarget());
93999         } else {
94000             // Ensure all items are rendered in the correct place in the correct column - this won't
94001             // get done by the column containers themselves if their dimensions are not changing.
94002             shadowItems.each(function(col) {
94003                 var layout = col.getLayout();
94004                 layout.renderItems(layout.getLayoutItems(), layout.getRenderTarget());
94005             });
94006         }
94007
94008         shadowCt.doComponentLayout();
94009     },
94010
94011
94012     // We don't want to render any items to the owner directly, that gets handled by each column's own layout
94013     renderItems: Ext.emptyFn,
94014
94015
94016     /**
94017      * @private
94018      * Creates and returns the shadow hbox container that will be used to arrange the owner's items
94019      * into columns.
94020      */
94021     getShadowCt: function() {
94022         var me = this,
94023             shadowCt = me.shadowCt,
94024             owner, items, item, columns, columnsIsArray, numCols, i;
94025
94026         if (!shadowCt) {
94027             // Create the column containers based on the owner's 'columns' config
94028             owner = me.owner;
94029             columns = owner.columns;
94030             columnsIsArray = Ext.isArray(columns);
94031             numCols = me.getColCount();
94032             items = [];
94033             for(i = 0; i < numCols; i++) {
94034                 item = {
94035                     xtype: 'container',
94036                     cls: owner.groupCls
94037                 };
94038                 if (columnsIsArray) {
94039                     // Array can contain mixture of whole numbers, used as fixed pixel widths, and fractional
94040                     // numbers, used as relative flex values.
94041                     if (columns[i] < 1) {
94042                         item.flex = columns[i];
94043                     } else {
94044                         item.width = columns[i];
94045                     }
94046                 }
94047                 else {
94048                     // All columns the same width
94049                     item.flex = 1;
94050                 }
94051                 items.push(item);
94052             }
94053
94054             // Create the shadow container; delay rendering until after items are added to the columns
94055             shadowCt = me.shadowCt = Ext.createWidget('container', {
94056                 layout: 'hbox',
94057                 items: items,
94058                 ownerCt: owner
94059             });
94060         }
94061         
94062         return shadowCt;
94063     },
94064
94065
94066     /**
94067      * @private Get the number of columns in the checkbox group
94068      */
94069     getColCount: function() {
94070         var owner = this.owner,
94071             colsCfg = owner.columns;
94072         return Ext.isArray(colsCfg) ? colsCfg.length : (Ext.isNumber(colsCfg) ? colsCfg : owner.items.length);
94073     }
94074
94075 });
94076
94077 /**
94078  * @class Ext.form.FieldContainer
94079  * @extends Ext.container.Container
94080
94081 FieldContainer is a derivation of {@link Ext.container.Container Container} that implements the
94082 {@link Ext.form.Labelable Labelable} mixin. This allows it to be configured so that it is rendered with
94083 a {@link #fieldLabel field label} and optional {@link #msgTarget error message} around its sub-items.
94084 This is useful for arranging a group of fields or other components within a single item in a form, so
94085 that it lines up nicely with other fields. A common use is for grouping a set of related fields under
94086 a single label in a form.
94087
94088 The container's configured {@link #items} will be layed out within the field body area according to the
94089 configured {@link #layout} type. The default layout is `'autocontainer'`.
94090
94091 Like regular fields, FieldContainer can inherit its decoration configuration from the
94092 {@link Ext.form.Panel#fieldDefaults fieldDefaults} of an enclosing FormPanel. In addition,
94093 FieldContainer itself can pass {@link #fieldDefaults} to any {@link Ext.form.Labelable fields}
94094 it may itself contain.
94095
94096 If you are grouping a set of {@link Ext.form.field.Checkbox Checkbox} or {@link Ext.form.field.Radio Radio}
94097 fields in a single labeled container, consider using a {@link Ext.form.CheckboxGroup}
94098 or {@link Ext.form.RadioGroup} instead as they are specialized for handling those types.
94099 {@img Ext.form.FieldContainer/Ext.form.FieldContainer1.png Ext.form.FieldContainer component}
94100 __Example usage:__
94101
94102     Ext.create('Ext.form.Panel', {
94103         title: 'FieldContainer Example',
94104         width: 550,
94105         bodyPadding: 10,
94106     
94107         items: [{
94108             xtype: 'fieldcontainer',
94109             fieldLabel: 'Last Three Jobs',
94110             labelWidth: 100,
94111     
94112             // The body area will contain three text fields, arranged
94113             // horizontally, separated by draggable splitters.
94114             layout: 'hbox',
94115             items: [{
94116                 xtype: 'textfield',
94117                 flex: 1
94118             }, {
94119                 xtype: 'splitter'
94120             }, {
94121                 xtype: 'textfield',
94122                 flex: 1
94123             }, {
94124                 xtype: 'splitter'
94125             }, {
94126                 xtype: 'textfield',
94127                 flex: 1
94128             }]
94129         }],
94130         renderTo: Ext.getBody()
94131     });
94132
94133 __Usage of {@link #fieldDefaults}:__
94134 {@img Ext.form.FieldContainer/Ext.form.FieldContainer2.png Ext.form.FieldContainer component}
94135
94136     Ext.create('Ext.form.Panel', {
94137         title: 'FieldContainer Example',
94138         width: 350,
94139         bodyPadding: 10,
94140     
94141         items: [{
94142             xtype: 'fieldcontainer',
94143             fieldLabel: 'Your Name',
94144             labelWidth: 75,
94145             defaultType: 'textfield',
94146     
94147             // Arrange fields vertically, stretched to full width
94148             layout: 'anchor',
94149             defaults: {
94150                 layout: '100%'
94151             },
94152     
94153             // These config values will be applied to both sub-fields, except
94154             // for Last Name which will use its own msgTarget.
94155             fieldDefaults: {
94156                 msgTarget: 'under',
94157                 labelAlign: 'top'
94158             },
94159     
94160             items: [{
94161                 fieldLabel: 'First Name',
94162                 name: 'firstName'
94163             }, {
94164                 fieldLabel: 'Last Name',
94165                 name: 'lastName',
94166                 msgTarget: 'under'
94167             }]
94168         }],
94169         renderTo: Ext.getBody()
94170     });
94171
94172
94173  * @constructor
94174  * Creates a new Ext.form.FieldContainer instance.
94175  * @param {Object} config The component configuration.
94176  *
94177  * @xtype fieldcontainer
94178  * @markdown
94179  * @docauthor Jason Johnston <jason@sencha.com>
94180  */
94181 Ext.define('Ext.form.FieldContainer', {
94182     extend: 'Ext.container.Container',
94183     mixins: {
94184         labelable: 'Ext.form.Labelable',
94185         fieldAncestor: 'Ext.form.FieldAncestor'
94186     },
94187     alias: 'widget.fieldcontainer',
94188
94189     componentLayout: 'field',
94190
94191     /**
94192      * @cfg {Boolean} combineLabels
94193      * If set to true, and there is no defined {@link #fieldLabel}, the field container will automatically
94194      * generate its label by combining the labels of all the fields it contains. Defaults to false.
94195      */
94196     combineLabels: false,
94197
94198     /**
94199      * @cfg {String} labelConnector
94200      * The string to use when joining the labels of individual sub-fields, when {@link #combineLabels} is
94201      * set to true. Defaults to ', '.
94202      */
94203     labelConnector: ', ',
94204
94205     /**
94206      * @cfg {Boolean} combineErrors
94207      * If set to true, the field container will automatically combine and display the validation errors from
94208      * all the fields it contains as a single error on the container, according to the configured
94209      * {@link #msgTarget}. Defaults to false.
94210      */
94211     combineErrors: false,
94212     
94213     maskOnDisable: false,
94214
94215     initComponent: function() {
94216         var me = this,
94217             onSubCmpAddOrRemove = me.onSubCmpAddOrRemove;
94218
94219         // Init mixins
94220         me.initLabelable();
94221         me.initFieldAncestor();
94222
94223         me.callParent();
94224     },
94225
94226     /**
94227      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
94228      * @param {Ext.form.Labelable} labelable The instance that was added
94229      */
94230     onLabelableAdded: function(labelable) {
94231         var me = this;
94232         me.mixins.fieldAncestor.onLabelableAdded.call(this, labelable);
94233         me.updateLabel();
94234     },
94235
94236     /**
94237      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
94238      * @param {Ext.form.Labelable} labelable The instance that was removed
94239      */
94240     onLabelableRemoved: function(labelable) {
94241         var me = this;
94242         me.mixins.fieldAncestor.onLabelableRemoved.call(this, labelable);
94243         me.updateLabel();
94244     },
94245
94246     onRender: function() {
94247         var me = this,
94248             renderSelectors = me.renderSelectors,
94249             applyIf = Ext.applyIf;
94250
94251         applyIf(renderSelectors, me.getLabelableSelectors());
94252
94253         me.callParent(arguments);
94254     },
94255
94256     initRenderTpl: function() {
94257         var me = this;
94258         if (!me.hasOwnProperty('renderTpl')) {
94259             me.renderTpl = me.getTpl('labelableRenderTpl');
94260         }
94261         return me.callParent();
94262     },
94263
94264     initRenderData: function() {
94265         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
94266     },
94267
94268     /**
94269      * Returns the combined field label if {@link #combineLabels} is set to true and if there is no
94270      * set {@link #fieldLabel}. Otherwise returns the fieldLabel like normal. You can also override
94271      * this method to provide a custom generated label.
94272      */
94273     getFieldLabel: function() {
94274         var label = this.fieldLabel || '';
94275         if (!label && this.combineLabels) {
94276             label = Ext.Array.map(this.query('[isFieldLabelable]'), function(field) {
94277                 return field.getFieldLabel();
94278             }).join(this.labelConnector);
94279         }
94280         return label;
94281     },
94282
94283     /**
94284      * @private Updates the content of the labelEl if it is rendered
94285      */
94286     updateLabel: function() {
94287         var me = this,
94288             label = me.labelEl;
94289         if (label) {
94290             label.update(me.getFieldLabel());
94291         }
94292     },
94293
94294
94295     /**
94296      * @private Fired when the error message of any field within the container changes, and updates the
94297      * combined error message to match.
94298      */
94299     onFieldErrorChange: function(field, activeError) {
94300         if (this.combineErrors) {
94301             var me = this,
94302                 oldError = me.getActiveError(),
94303                 invalidFields = Ext.Array.filter(me.query('[isFormField]'), function(field) {
94304                     return field.hasActiveError();
94305                 }),
94306                 newErrors = me.getCombinedErrors(invalidFields);
94307
94308             if (newErrors) {
94309                 me.setActiveErrors(newErrors);
94310             } else {
94311                 me.unsetActiveError();
94312             }
94313
94314             if (oldError !== me.getActiveError()) {
94315                 me.doComponentLayout();
94316             }
94317         }
94318     },
94319
94320     /**
94321      * Takes an Array of invalid {@link Ext.form.field.Field} objects and builds a combined list of error
94322      * messages from them. Defaults to prepending each message by the field name and a colon. This
94323      * can be overridden to provide custom combined error message handling, for instance changing
94324      * the format of each message or sorting the array (it is sorted in order of appearance by default).
94325      * @param {Array} invalidFields An Array of the sub-fields which are currently invalid.
94326      * @return {Array} The combined list of error messages
94327      */
94328     getCombinedErrors: function(invalidFields) {
94329         var forEach = Ext.Array.forEach,
94330             errors = [];
94331         forEach(invalidFields, function(field) {
94332             forEach(field.getActiveErrors(), function(error) {
94333                 var label = field.getFieldLabel();
94334                 errors.push((label ? label + ': ' : '') + error);
94335             });
94336         });
94337         return errors;
94338     },
94339
94340     getTargetEl: function() {
94341         return this.bodyEl || this.callParent();
94342     }
94343 });
94344
94345 /**
94346  * @class Ext.form.CheckboxGroup
94347  * @extends Ext.form.FieldContainer
94348  * <p>A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
94349  * {@link Ext.form.field.Checkbox} controls into columns, and provides convenience {@link Ext.form.field.Field} methods
94350  * for {@link #getValue getting}, {@link #setValue setting}, and {@link #validate validating} the group
94351  * of checkboxes as a whole.</p>
94352  * <p><b>Validation:</b> Individual checkbox fields themselves have no default validation behavior, but
94353  * sometimes you want to require a user to select at least one of a group of checkboxes. CheckboxGroup
94354  * allows this by setting the config <tt>{@link #allowBlank}:false</tt>; when the user does not check at
94355  * least one of the checkboxes, the entire group will be highlighted as invalid and the
94356  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.</p>
94357  * <p><b>Layout:</b> The default layout for CheckboxGroup makes it easy to arrange the checkboxes into
94358  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
94359  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
94360  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
94361  * the checkbox components at any depth will still be managed by the CheckboxGroup's validation.</p>
94362  * {@img Ext.form.RadioGroup/Ext.form.RadioGroup.png Ext.form.RadioGroup component}
94363  * <p>Example usage:</p>
94364  * <pre><code>
94365 Ext.create('Ext.form.Panel', {
94366     title: 'RadioGroup Example',
94367     width: 300,
94368     height: 125,
94369     bodyPadding: 10,
94370     renderTo: Ext.getBody(),        
94371     items:[{            
94372         xtype: 'radiogroup',
94373         fieldLabel: 'Two Columns',
94374         // Arrange radio buttons into two columns, distributed vertically
94375         columns: 2,
94376         vertical: true,
94377         items: [
94378             {boxLabel: 'Item 1', name: 'rb', inputValue: '1'},
94379             {boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},
94380             {boxLabel: 'Item 3', name: 'rb', inputValue: '3'},
94381             {boxLabel: 'Item 4', name: 'rb', inputValue: '4'},
94382             {boxLabel: 'Item 5', name: 'rb', inputValue: '5'},
94383             {boxLabel: 'Item 6', name: 'rb', inputValue: '6'}
94384         ]
94385     }]
94386 });
94387  * </code></pre>
94388  * @constructor
94389  * Creates a new CheckboxGroup
94390  * @param {Object} config Configuration options
94391  * @xtype checkboxgroup
94392  */
94393 Ext.define('Ext.form.CheckboxGroup', {
94394     extend:'Ext.form.FieldContainer',
94395     mixins: {
94396         field: 'Ext.form.field.Field'
94397     },
94398     alias: 'widget.checkboxgroup',
94399     requires: ['Ext.layout.container.CheckboxGroup', 'Ext.form.field.Base'],
94400
94401     /**
94402      * @cfg {String} name
94403      * @hide
94404      */
94405
94406     /**
94407      * @cfg {Array} items An Array of {@link Ext.form.field.Checkbox Checkbox}es or Checkbox config objects
94408      * to arrange in the group.
94409      */
94410
94411     /**
94412      * @cfg {String/Number/Array} columns Specifies the number of columns to use when displaying grouped
94413      * checkbox/radio controls using automatic layout.  This config can take several types of values:
94414      * <ul><li><b>'auto'</b> : <p class="sub-desc">The controls will be rendered one per column on one row and the width
94415      * of each column will be evenly distributed based on the width of the overall field container. This is the default.</p></li>
94416      * <li><b>Number</b> : <p class="sub-desc">If you specific a number (e.g., 3) that number of columns will be
94417      * created and the contained controls will be automatically distributed based on the value of {@link #vertical}.</p></li>
94418      * <li><b>Array</b> : <p class="sub-desc">You can also specify an array of column widths, mixing integer
94419      * (fixed width) and float (percentage width) values as needed (e.g., [100, .25, .75]). Any integer values will
94420      * be rendered first, then any float values will be calculated as a percentage of the remaining space. Float
94421      * values do not have to add up to 1 (100%) although if you want the controls to take up the entire field
94422      * container you should do so.</p></li></ul>
94423      */
94424     columns : 'auto',
94425
94426     /**
94427      * @cfg {Boolean} vertical True to distribute contained controls across columns, completely filling each column
94428      * top to bottom before starting on the next column.  The number of controls in each column will be automatically
94429      * calculated to keep columns as even as possible.  The default value is false, so that controls will be added
94430      * to columns one at a time, completely filling each row left to right before starting on the next row.
94431      */
94432     vertical : false,
94433
94434     /**
94435      * @cfg {Boolean} allowBlank False to validate that at least one item in the group is checked (defaults to true).
94436      * If no items are selected at validation time, {@link #blankText} will be used as the error text.
94437      */
94438     allowBlank : true,
94439
94440     /**
94441      * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails (defaults to "You must
94442      * select at least one item in this group")
94443      */
94444     blankText : "You must select at least one item in this group",
94445
94446     // private
94447     defaultType : 'checkboxfield',
94448
94449     // private
94450     groupCls : Ext.baseCSSPrefix + 'form-check-group',
94451
94452     /**
94453      * @cfg {String} fieldBodyCls
94454      * An extra CSS class to be applied to the body content element in addition to {@link #baseBodyCls}.
94455      * Defaults to 'x-form-checkboxgroup-body'.
94456      */
94457     fieldBodyCls: Ext.baseCSSPrefix + 'form-checkboxgroup-body',
94458
94459     // private
94460     layout: 'checkboxgroup',
94461
94462     initComponent: function() {
94463         var me = this;
94464         me.callParent();
94465         me.initField();
94466     },
94467
94468     /**
94469      * @protected
94470      * Initializes the field's value based on the initial config. If the {@link #value} config is specified
94471      * then we use that to set the value; otherwise we initialize the originalValue by querying the values of
94472      * all sub-checkboxes after they have been initialized.
94473      */
94474     initValue: function() {
94475         var me = this,
94476             valueCfg = me.value;
94477         me.originalValue = me.lastValue = valueCfg || me.getValue();
94478         if (valueCfg) {
94479             me.setValue(valueCfg);
94480         }
94481     },
94482
94483     /**
94484      * @protected
94485      * When a checkbox is added to the group, monitor it for changes
94486      */
94487     onFieldAdded: function(field) {
94488         var me = this;
94489         if (field.isCheckbox) {
94490             me.mon(field, 'change', me.checkChange, me);
94491         }
94492         me.callParent(arguments);
94493     },
94494
94495     onFieldRemoved: function(field) {
94496         var me = this;
94497         if (field.isCheckbox) {
94498             me.mun(field, 'change', me.checkChange, me);
94499         }
94500         me.callParent(arguments);
94501     },
94502
94503     // private override - the group value is a complex object, compare using object serialization
94504     isEqual: function(value1, value2) {
94505         var toQueryString = Ext.Object.toQueryString;
94506         return toQueryString(value1) === toQueryString(value2);
94507     },
94508
94509     /**
94510      * Runs CheckboxGroup's validations and returns an array of any errors. The only error by default
94511      * is if allowBlank is set to true and no items are checked.
94512      * @return {Array} Array of all validation errors
94513      */
94514     getErrors: function() {
94515         var errors = [];
94516         if (!this.allowBlank && Ext.isEmpty(this.getChecked())) {
94517             errors.push(this.blankText);
94518         }
94519         return errors;
94520     },
94521
94522     /**
94523      * @private Returns all checkbox components within the container
94524      */
94525     getBoxes: function() {
94526         return this.query('[isCheckbox]');
94527     },
94528
94529     /**
94530      * @private Convenience function which calls the given function for every checkbox in the group
94531      * @param {Function} fn The function to call
94532      * @param {Object} scope Optional scope object
94533      */
94534     eachBox: function(fn, scope) {
94535         Ext.Array.forEach(this.getBoxes(), fn, scope || this);
94536     },
94537
94538     /**
94539      * Returns an Array of all checkboxes in the container which are currently checked
94540      * @return {Array} Array of Ext.form.field.Checkbox components
94541      */
94542     getChecked: function() {
94543         return Ext.Array.filter(this.getBoxes(), function(cb) {
94544             return cb.getValue();
94545         });
94546     },
94547
94548     // private override
94549     isDirty: function(){
94550         return Ext.Array.some(this.getBoxes(), function(cb) {
94551             return cb.isDirty();
94552         });
94553     },
94554
94555     // private override
94556     setReadOnly: function(readOnly) {
94557         this.eachBox(function(cb) {
94558             cb.setReadOnly(readOnly);
94559         });
94560         this.readOnly = readOnly;
94561     },
94562
94563     /**
94564      * Resets the checked state of all {@link Ext.form.field.Checkbox checkboxes} in the group to their
94565      * originally loaded values and clears any validation messages.
94566      * See {@link Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
94567      */
94568     reset: function() {
94569         var me = this,
94570             hadError = me.hasActiveError(),
94571             preventMark = me.preventMark;
94572         me.preventMark = true;
94573         me.batchChanges(function() {
94574             me.eachBox(function(cb) {
94575                 cb.reset();
94576             });
94577         });
94578         me.preventMark = preventMark;
94579         me.unsetActiveError();
94580         if (hadError) {
94581             me.doComponentLayout();
94582         }
94583     },
94584
94585     // private override
94586     resetOriginalValue: function() {
94587         // Defer resetting of originalValue until after all sub-checkboxes have been reset so we get
94588         // the correct data from getValue()
94589         Ext.defer(function() {
94590             this.callParent();
94591         }, 1, this);
94592     },
94593
94594
94595     /**
94596      * <p>Sets the value(s) of all checkboxes in the group. The expected format is an Object of
94597      * name-value pairs corresponding to the names of the checkboxes in the group. Each pair can
94598      * have either a single or multiple values:</p>
94599      * <ul>
94600      *   <li>A single Boolean or String value will be passed to the <code>setValue</code> method of the
94601      *   checkbox with that name. See the rules in {@link Ext.form.field.Checkbox#setValue} for accepted values.</li>
94602      *   <li>An Array of String values will be matched against the {@link Ext.form.field.Checkbox#inputValue inputValue}
94603      *   of checkboxes in the group with that name; those checkboxes whose inputValue exists in the array will be
94604      *   checked and others will be unchecked.</li>
94605      * </ul>
94606      * <p>If a checkbox's name is not in the mapping at all, it will be unchecked.</p>
94607      * <p>An example:</p>
94608      * <pre><code>var myCheckboxGroup = new Ext.form.CheckboxGroup({
94609     columns: 3,
94610     items: [{
94611         name: 'cb1',
94612         boxLabel: 'Single 1'
94613     }, {
94614         name: 'cb2',
94615         boxLabel: 'Single 2'
94616     }, {
94617         name: 'cb3',
94618         boxLabel: 'Single 3'
94619     }, {
94620         name: 'cbGroup',
94621         boxLabel: 'Grouped 1'
94622         inputValue: 'value1'
94623     }, {
94624         name: 'cbGroup',
94625         boxLabel: 'Grouped 2'
94626         inputValue: 'value2'
94627     }, {
94628         name: 'cbGroup',
94629         boxLabel: 'Grouped 3'
94630         inputValue: 'value3'
94631     }]
94632 });
94633
94634 myCheckboxGroup.setValue({
94635     cb1: true,
94636     cb3: false,
94637     cbGroup: ['value1', 'value3']
94638 });</code></pre>
94639      * <p>The above code will cause the checkbox named 'cb1' to be checked, as well as the first and third
94640      * checkboxes named 'cbGroup'. The other three checkboxes will be unchecked.</p>
94641      * @param {Object} value The mapping of checkbox names to values.
94642      * @return {Ext.form.CheckboxGroup} this
94643      */
94644     setValue: function(value) {
94645         var me = this;
94646         me.batchChanges(function() {
94647             me.eachBox(function(cb) {
94648                 var name = cb.getName(),
94649                     cbValue = false;
94650                 if (value && name in value) {
94651                     if (Ext.isArray(value[name])) {
94652                         cbValue = Ext.Array.contains(value[name], cb.inputValue);
94653                     } else {
94654                         // single value, let the checkbox's own setValue handle conversion
94655                         cbValue = value[name];
94656                     }
94657                 }
94658                 cb.setValue(cbValue);
94659             });
94660         });
94661         return me;
94662     },
94663
94664
94665     /**
94666      * <p>Returns an object containing the values of all checked checkboxes within the group. Each key-value pair
94667      * in the object corresponds to a checkbox {@link Ext.form.field.Checkbox#name name}. If there is only one checked
94668      * checkbox with a particular name, the value of that pair will be the String
94669      * {@link Ext.form.field.Checkbox#inputValue inputValue} of that checkbox. If there are multiple checked checkboxes
94670      * with that name, the value of that pair will be an Array of the selected inputValues.</p>
94671      * <p>The object format returned from this method can also be passed directly to the {@link #setValue} method.</p>
94672      * <p>NOTE: In Ext 3, this method returned an array of Checkbox components; this was changed to make it more
94673      * consistent with other field components and with the {@link #setValue} argument signature. If you need the old
94674      * behavior in Ext 4+, use the {@link #getChecked} method instead.</p>
94675      */
94676     getValue: function() {
94677         var values = {};
94678         this.eachBox(function(cb) {
94679             var name = cb.getName(),
94680                 inputValue = cb.inputValue,
94681                 bucket;
94682             if (cb.getValue()) {
94683                 if (name in values) {
94684                     bucket = values[name];
94685                     if (!Ext.isArray(bucket)) {
94686                         bucket = values[name] = [bucket];
94687                     }
94688                     bucket.push(inputValue);
94689                 } else {
94690                     values[name] = inputValue;
94691                 }
94692             }
94693         });
94694         return values;
94695     },
94696
94697     /*
94698      * Don't return any data for submit; the form will get the info from the individual checkboxes themselves.
94699      */
94700     getSubmitData: function() {
94701         return null;
94702     },
94703
94704     /*
94705      * Don't return any data for the model; the form will get the info from the individual checkboxes themselves.
94706      */
94707     getModelData: function() {
94708         return null;
94709     },
94710
94711     validate: function() {
94712         var me = this,
94713             errors = me.getErrors(),
94714             isValid = Ext.isEmpty(errors),
94715             wasValid = !me.hasActiveError();
94716
94717         if (isValid) {
94718             me.unsetActiveError();
94719         } else {
94720             me.setActiveError(errors);
94721         }
94722         if (isValid !== wasValid) {
94723             me.fireEvent('validitychange', me, isValid);
94724             me.doComponentLayout();
94725         }
94726
94727         return isValid;
94728     }
94729
94730 }, function() {
94731
94732     this.borrow(Ext.form.field.Base, ['markInvalid', 'clearInvalid']);
94733
94734 });
94735
94736
94737 /**
94738  * @private
94739  * Private utility class for managing all {@link Ext.form.field.Checkbox} fields grouped by name.
94740  */
94741 Ext.define('Ext.form.CheckboxManager', {
94742     extend: 'Ext.util.MixedCollection',
94743     singleton: true,
94744
94745     getByName: function(name) {
94746         return this.filterBy(function(item) {
94747             return item.name == name;
94748         });
94749     },
94750
94751     getWithValue: function(name, value) {
94752         return this.filterBy(function(item) {
94753             return item.name == name && item.inputValue == value;
94754         });
94755     },
94756
94757     getChecked: function(name) {
94758         return this.filterBy(function(item) {
94759             return item.name == name && item.checked;
94760         });
94761     }
94762 });
94763
94764 /**
94765  * @class Ext.form.FieldSet
94766  * @extends Ext.container.Container
94767  * 
94768  * A container for grouping sets of fields, rendered as a HTML `fieldset` element. The {@link #title}
94769  * config will be rendered as the fieldset's `legend`.
94770  * 
94771  * While FieldSets commonly contain simple groups of fields, they are general {@link Ext.container.Container Containers}
94772  * and may therefore contain any type of components in their {@link #items}, including other nested containers.
94773  * The default {@link #layout} for the FieldSet's items is `'anchor'`, but it can be configured to use any other
94774  * layout type.
94775  * 
94776  * FieldSets may also be collapsed if configured to do so; this can be done in two ways:
94777  * 
94778  * 1. Set the {@link #collapsible} config to true; this will result in a collapse button being rendered next to
94779  *    the {@link #title legend title}, or:
94780  * 2. Set the {@link #checkboxToggle} config to true; this is similar to using {@link #collapsible} but renders
94781  *    a {@link Ext.form.field.Checkbox checkbox} in place of the toggle button. The fieldset will be expanded when the
94782  *    checkbox is checked and collapsed when it is unchecked. The checkbox will also be included in the
94783  *    {@link Ext.form.Basic#submit form submit parameters} using the {@link #checkboxName} as its parameter name.
94784  *
94785  * {@img Ext.form.FieldSet/Ext.form.FieldSet.png Ext.form.FieldSet component}
94786  *
94787  * ## Example usage
94788  * 
94789  *     Ext.create('Ext.form.Panel', {
94790  *         title: 'Simple Form with FieldSets',
94791  *         labelWidth: 75, // label settings here cascade unless overridden
94792  *         url: 'save-form.php',
94793  *         frame: true,
94794  *         bodyStyle: 'padding:5px 5px 0',
94795  *         width: 550,
94796  *         renderTo: Ext.getBody(),
94797  *         layout: 'column', // arrange fieldsets side by side
94798  *         defaults: {
94799  *             bodyPadding: 4
94800  *         },
94801  *         items: [{
94802  *             // Fieldset in Column 1 - collapsible via toggle button
94803  *             xtype:'fieldset',
94804  *             columnWidth: 0.5,
94805  *             title: 'Fieldset 1',
94806  *             collapsible: true,
94807  *             defaultType: 'textfield',
94808  *             defaults: {anchor: '100%'},
94809  *             layout: 'anchor',
94810  *             items :[{
94811  *                 fieldLabel: 'Field 1',
94812  *                 name: 'field1'
94813  *             }, {
94814  *                 fieldLabel: 'Field 2',
94815  *                 name: 'field2'
94816  *             }]
94817  *         }, {
94818  *             // Fieldset in Column 2 - collapsible via checkbox, collapsed by default, contains a panel
94819  *             xtype:'fieldset',
94820  *             title: 'Show Panel', // title or checkboxToggle creates fieldset header
94821  *             columnWidth: 0.5,
94822  *             checkboxToggle: true,
94823  *             collapsed: true, // fieldset initially collapsed
94824  *             layout:'anchor',
94825  *             items :[{
94826  *                 xtype: 'panel',
94827  *                 anchor: '100%',
94828  *                 title: 'Panel inside a fieldset',
94829  *                 frame: true,
94830  *                 height: 52
94831  *             }]
94832  *         }]
94833  *     });
94834  * 
94835  * @constructor
94836  * Create a new FieldSet
94837  * @param {Object} config Configuration options
94838  * @xtype fieldset
94839  * @docauthor Jason Johnston <jason@sencha.com>
94840  */
94841 Ext.define('Ext.form.FieldSet', {
94842     extend: 'Ext.container.Container',
94843     alias: 'widget.fieldset',
94844     uses: ['Ext.form.field.Checkbox', 'Ext.panel.Tool', 'Ext.layout.container.Anchor', 'Ext.layout.component.FieldSet'],
94845
94846     /**
94847      * @cfg {String} title
94848      * A title to be displayed in the fieldset's legend. May contain HTML markup.
94849      */
94850
94851     /**
94852      * @cfg {Boolean} checkboxToggle
94853      * Set to <tt>true</tt> to render a checkbox into the fieldset frame just
94854      * in front of the legend to expand/collapse the fieldset when the checkbox is toggled. (defaults
94855      * to <tt>false</tt>). This checkbox will be included in form submits using the {@link #checkboxName}.
94856      */
94857
94858     /**
94859      * @cfg {String} checkboxName
94860      * The name to assign to the fieldset's checkbox if <tt>{@link #checkboxToggle} = true</tt>
94861      * (defaults to <tt>'[fieldset id]-checkbox'</tt>).
94862      */
94863
94864     /**
94865      * @cfg {Boolean} collapsible
94866      * Set to <tt>true</tt> to make the fieldset collapsible and have the expand/collapse toggle button automatically
94867      * rendered into the legend element, <tt>false</tt> to keep the fieldset statically sized with no collapse
94868      * button (defaults to <tt>false</tt>). Another option is to configure <tt>{@link #checkboxToggle}</tt>.
94869      * Use the {@link #collapsed} config to collapse the fieldset by default.
94870      */
94871
94872     /**
94873      * @cfg {Boolean} collapsed
94874      * Set to <tt>true</tt> to render the fieldset as collapsed by default. If {@link #checkboxToggle} is specified,
94875      * the checkbox will also be unchecked by default.
94876      */
94877     collapsed: false,
94878
94879     /**
94880      * @property legend
94881      * @type Ext.Component
94882      * The component for the fieldset's legend. Will only be defined if the configuration requires a legend
94883      * to be created, by setting the {@link #title} or {@link #checkboxToggle} options.
94884      */
94885
94886     /**
94887      * @cfg {String} baseCls The base CSS class applied to the fieldset (defaults to <tt>'x-fieldset'</tt>).
94888      */
94889     baseCls: Ext.baseCSSPrefix + 'fieldset',
94890
94891     /**
94892      * @cfg {String} layout The {@link Ext.container.Container#layout} for the fieldset's immediate child items.
94893      * Defaults to <tt>'anchor'</tt>.
94894      */
94895     layout: 'anchor',
94896
94897     componentLayout: 'fieldset',
94898
94899     // No aria role necessary as fieldset has its own recognized semantics
94900     ariaRole: '',
94901
94902     renderTpl: ['<div class="{baseCls}-body"></div>'],
94903     
94904     maskOnDisable: false,
94905
94906     getElConfig: function(){
94907         return {tag: 'fieldset', id: this.id};
94908     },
94909
94910     initComponent: function() {
94911         var me = this,
94912             baseCls = me.baseCls;
94913
94914         me.callParent();
94915
94916         // Create the Legend component if needed
94917         me.initLegend();
94918
94919         // Add body el selector
94920         Ext.applyIf(me.renderSelectors, {
94921             body: '.' + baseCls + '-body'
94922         });
94923
94924         if (me.collapsed) {
94925             me.addCls(baseCls + '-collapsed');
94926             me.collapse();
94927         }
94928     },
94929
94930     // private
94931     onRender: function(container, position) {
94932         this.callParent(arguments);
94933         // Make sure the legend is created and rendered
94934         this.initLegend();
94935     },
94936
94937     /**
94938      * @private
94939      * Initialize and render the legend component if necessary
94940      */
94941     initLegend: function() {
94942         var me = this,
94943             legendItems,
94944             legend = me.legend;
94945
94946         // Create the legend component if needed and it hasn't been already
94947         if (!legend && (me.title || me.checkboxToggle || me.collapsible)) {
94948             legendItems = [];
94949
94950             // Checkbox
94951             if (me.checkboxToggle) {
94952                 legendItems.push(me.createCheckboxCmp());
94953             }
94954             // Toggle button
94955             else if (me.collapsible) {
94956                 legendItems.push(me.createToggleCmp());
94957             }
94958
94959             // Title
94960             legendItems.push(me.createTitleCmp());
94961
94962             legend = me.legend = Ext.create('Ext.container.Container', {
94963                 baseCls: me.baseCls + '-header',
94964                 ariaRole: '',
94965                 getElConfig: function(){
94966                     return {tag: 'legend', cls: this.baseCls};
94967                 },
94968                 items: legendItems
94969             });
94970         }
94971
94972         // Make sure legend is rendered if the fieldset is rendered
94973         if (legend && !legend.rendered && me.rendered) {
94974             me.legend.render(me.el, me.body); //insert before body element
94975         }
94976     },
94977
94978     /**
94979      * @protected
94980      * Creates the legend title component. This is only called internally, but could be overridden in subclasses
94981      * to customize the title component.
94982      * @return Ext.Component
94983      */
94984     createTitleCmp: function() {
94985         var me = this;
94986         me.titleCmp = Ext.create('Ext.Component', {
94987             html: me.title,
94988             cls: me.baseCls + '-header-text'
94989         });
94990         return me.titleCmp;
94991         
94992     },
94993
94994     /**
94995      * @property checkboxCmp
94996      * @type Ext.form.field.Checkbox
94997      * Refers to the {@link Ext.form.field.Checkbox} component that is added next to the title in the legend. Only
94998      * populated if the fieldset is configured with <tt>{@link #checkboxToggle}:true</tt>.
94999      */
95000
95001     /**
95002      * @protected
95003      * Creates the checkbox component. This is only called internally, but could be overridden in subclasses
95004      * to customize the checkbox's configuration or even return an entirely different component type.
95005      * @return Ext.Component
95006      */
95007     createCheckboxCmp: function() {
95008         var me = this,
95009             suffix = '-checkbox';
95010             
95011         me.checkboxCmp = Ext.create('Ext.form.field.Checkbox', {
95012             name: me.checkboxName || me.id + suffix,
95013             cls: me.baseCls + '-header' + suffix,
95014             checked: !me.collapsed,
95015             listeners: {
95016                 change: me.onCheckChange,
95017                 scope: me
95018             }
95019         });
95020         return me.checkboxCmp;
95021     },
95022
95023     /**
95024      * @property toggleCmp
95025      * @type Ext.panel.Tool
95026      * Refers to the {@link Ext.panel.Tool} component that is added as the collapse/expand button next
95027      * to the title in the legend. Only populated if the fieldset is configured with <tt>{@link #collapsible}:true</tt>.
95028      */
95029
95030     /**
95031      * @protected
95032      * Creates the toggle button component. This is only called internally, but could be overridden in
95033      * subclasses to customize the toggle component.
95034      * @return Ext.Component
95035      */
95036     createToggleCmp: function() {
95037         var me = this;
95038         me.toggleCmp = Ext.create('Ext.panel.Tool', {
95039             type: 'toggle',
95040             handler: me.toggle,
95041             scope: me
95042         });
95043         return me.toggleCmp;
95044     },
95045     
95046     /**
95047      * Sets the title of this fieldset
95048      * @param {String} title The new title
95049      * @return {Ext.form.FieldSet} this
95050      */
95051     setTitle: function(title) {
95052         var me = this;
95053         me.title = title;
95054         me.initLegend();
95055         me.titleCmp.update(title);
95056         return me;
95057     },
95058     
95059     getTargetEl : function() {
95060         return this.body || this.frameBody || this.el;
95061     },
95062     
95063     getContentTarget: function() {
95064         return this.body;
95065     },
95066     
95067     /**
95068      * @private
95069      * Include the legend component in the items for ComponentQuery
95070      */
95071     getRefItems: function(deep) {
95072         var refItems = this.callParent(arguments),
95073             legend = this.legend;
95074
95075         // Prepend legend items to ensure correct order
95076         if (legend) {
95077             refItems.unshift(legend);
95078             if (deep) {
95079                 refItems.unshift.apply(refItems, legend.getRefItems(true));
95080             }
95081         }
95082         return refItems;
95083     },
95084
95085     /**
95086      * Expands the fieldset.
95087      * @return {Ext.form.FieldSet} this
95088      */
95089     expand : function(){
95090         return this.setExpanded(true);
95091     },
95092     
95093     /**
95094      * Collapses the fieldset.
95095      * @return {Ext.form.FieldSet} this
95096      */
95097     collapse : function() {
95098         return this.setExpanded(false);
95099     },
95100
95101     /**
95102      * @private Collapse or expand the fieldset
95103      */
95104     setExpanded: function(expanded) {
95105         var me = this,
95106             checkboxCmp = me.checkboxCmp;
95107
95108         expanded = !!expanded;
95109         
95110         if (checkboxCmp) {
95111             checkboxCmp.setValue(expanded);
95112         }
95113         
95114         if (expanded) {
95115             me.removeCls(me.baseCls + '-collapsed');
95116         } else {
95117             me.addCls(me.baseCls + '-collapsed');
95118         }
95119         me.collapsed = !expanded;
95120         if (expanded) {
95121             // ensure subitems will get rendered and layed out when expanding
95122             me.getComponentLayout().childrenChanged = true;
95123         }
95124         me.doComponentLayout();
95125         return me;
95126     },
95127
95128     /**
95129      * Toggle the fieldset's collapsed state to the opposite of what it is currently
95130      */
95131     toggle: function() {
95132         this.setExpanded(!!this.collapsed);
95133     },
95134
95135     /**
95136      * @private Handle changes in the checkbox checked state
95137      */
95138     onCheckChange: function(cmp, checked) {
95139         this.setExpanded(checked);
95140     },
95141
95142     beforeDestroy : function() {
95143         var legend = this.legend;
95144         if (legend) {
95145             legend.destroy();
95146         }
95147         this.callParent();
95148     }
95149 });
95150
95151 /**
95152  * @class Ext.form.Label
95153  * @extends Ext.Component
95154
95155 Produces a standalone `<label />` element which can be inserted into a form and be associated with a field
95156 in that form using the {@link #forId} property.
95157
95158 **NOTE:** in most cases it will be more appropriate to use the {@link Ext.form.Labelable#fieldLabel fieldLabel}
95159 and associated config properties ({@link Ext.form.Labelable#labelAlign}, {@link Ext.form.Labelable#labelWidth},
95160 etc.) in field components themselves, as that allows labels to be uniformly sized throughout the form.
95161 Ext.form.Label should only be used when your layout can not be achieved with the standard
95162 {@link Ext.form.Labelable field layout}.
95163
95164 You will likely be associating the label with a field component that extends {@link Ext.form.field.Base}, so
95165 you should make sure the {@link #forId} is set to the same value as the {@link Ext.form.field.Base#inputId inputId}
95166 of that field.
95167
95168 The label's text can be set using either the {@link #text} or {@link #html} configuration properties; the
95169 difference between the two is that the former will automatically escape HTML characters when rendering, while
95170 the latter will not.
95171 {@img Ext.form.Label/Ext.form.Label.png Ext.form.Label component}
95172 #Example usage:#
95173
95174 This example creates a Label after its associated Text field, an arrangement that cannot currently
95175 be achieved using the standard Field layout's labelAlign.
95176
95177     Ext.create('Ext.form.Panel', {
95178         title: 'Field with Label',
95179         width: 400,
95180         bodyPadding: 10,
95181         renderTo: Ext.getBody(),
95182         layout: {
95183             type: 'hbox',
95184             align: 'middle'
95185         },
95186         items: [{
95187             xtype: 'textfield',
95188             hideLabel: true,
95189             flex: 1
95190         }, {
95191             xtype: 'label',
95192             forId: 'myFieldId',
95193             text: 'My Awesome Field',
95194             margins: '0 0 0 10'
95195         }]
95196     });
95197
95198  * @constructor
95199  * Creates a new Label component.
95200  * @param {Ext.core.Element/String/Object} config The configuration options.
95201  * 
95202  * @xtype label
95203  * @markdown
95204  * @docauthor Jason Johnston <jason@sencha.com>
95205  */
95206 Ext.define('Ext.form.Label', {
95207     extend:'Ext.Component',
95208     alias: 'widget.label',
95209     requires: ['Ext.util.Format'],
95210
95211     /**
95212      * @cfg {String} text The plain text to display within the label (defaults to ''). If you need to include HTML
95213      * tags within the label's innerHTML, use the {@link #html} config instead.
95214      */
95215     /**
95216      * @cfg {String} forId The id of the input element to which this label will be bound via the standard HTML 'for'
95217      * attribute. If not specified, the attribute will not be added to the label. In most cases you will be
95218      * associating the label with a {@link Ext.form.field.Base} component, so you should make sure this matches
95219      * the {@link Ext.form.field.Base#inputId inputId} of that field.
95220      */
95221     /**
95222      * @cfg {String} html An HTML fragment that will be used as the label's innerHTML (defaults to '').
95223      * Note that if {@link #text} is specified it will take precedence and this value will be ignored.
95224      */
95225     
95226     maskOnDisable: false,
95227     getElConfig: function(){
95228         var me = this;
95229         return {
95230             tag: 'label', 
95231             id: me.id, 
95232             htmlFor: me.forId || '',
95233             html: me.text ? Ext.util.Format.htmlEncode(me.text) : (me.html || '') 
95234         };
95235     },
95236
95237     /**
95238      * Updates the label's innerHTML with the specified string.
95239      * @param {String} text The new label text
95240      * @param {Boolean} encode (optional) False to skip HTML-encoding the text when rendering it
95241      * to the label (defaults to true which encodes the value). This might be useful if you want to include
95242      * tags in the label's innerHTML rather than rendering them as string literals per the default logic.
95243      * @return {Label} this
95244      */
95245     setText : function(text, encode){
95246         var me = this;
95247         
95248         encode = encode !== false;
95249         if(encode) {
95250             me.text = text;
95251             delete me.html;
95252         } else {
95253             me.html = text;
95254             delete me.text;
95255         }
95256         
95257         if(me.rendered){
95258             me.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(text) : text;
95259         }
95260         return this;
95261     }
95262 });
95263
95264
95265 /**
95266  * @class Ext.form.Panel
95267  * @extends Ext.panel.Panel
95268
95269 FormPanel provides a standard container for forms. It is essentially a standard {@link Ext.panel.Panel} which
95270 automatically creates a {@link Ext.form.Basic BasicForm} for managing any {@link Ext.form.field.Field}
95271 objects that are added as descendants of the panel. It also includes conveniences for configuring and
95272 working with the BasicForm and the collection of Fields.
95273
95274 __Layout__
95275
95276 By default, FormPanel is configured with `{@link Ext.layout.container.Anchor layout:'anchor'}` for
95277 the layout of its immediate child items. This can be changed to any of the supported container layouts.
95278 The layout of sub-containers is configured in {@link Ext.container.Container#layout the standard way}.
95279
95280 __BasicForm__
95281
95282 Although **not listed** as configuration options of FormPanel, the FormPanel class accepts all
95283 of the config options supported by the {@link Ext.form.Basic} class, and will pass them along to
95284 the internal BasicForm when it is created.
95285
95286 **Note**: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
95287 the `initialConfig` property of the FormPanel. Applying {@link Ext.form.Basic BasicForm}
95288 configuration settings to `this` will *not* affect the BasicForm's configuration.
95289
95290 The following events fired by the BasicForm will be re-fired by the FormPanel and can therefore be
95291 listened for on the FormPanel itself:
95292
95293 - {@link Ext.form.Basic#beforeaction beforeaction}
95294 - {@link Ext.form.Basic#actionfailed actionfailed}
95295 - {@link Ext.form.Basic#actioncomplete actioncomplete}
95296 - {@link Ext.form.Basic#validitychange validitychange}
95297 - {@link Ext.form.Basic#dirtychange dirtychange}
95298
95299 __Field Defaults__
95300
95301 The {@link #fieldDefaults} config option conveniently allows centralized configuration of default values
95302 for all fields added as descendants of the FormPanel. Any config option recognized by implementations
95303 of {@link Ext.form.Labelable} may be included in this object. See the {@link #fieldDefaults} documentation
95304 for details of how the defaults are applied.
95305
95306 __Form Validation__
95307
95308 With the default configuration, form fields are validated on-the-fly while the user edits their values.
95309 This can be controlled on a per-field basis (or via the {@link #fieldDefaults} config) with the field
95310 config properties {@link Ext.form.field.Field#validateOnChange} and {@link Ext.form.field.Base#checkChangeEvents},
95311 and the FormPanel's config properties {@link #pollForChanges} and {@link #pollInterval}.
95312
95313 Any component within the FormPanel can be configured with `formBind: true`. This will cause that
95314 component to be automatically disabled when the form is invalid, and enabled when it is valid. This is most
95315 commonly used for Button components to prevent submitting the form in an invalid state, but can be used on
95316 any component type.
95317
95318 For more information on form validation see the following:
95319
95320 - {@link Ext.form.field.Field#validateOnChange}
95321 - {@link #pollForChanges} and {@link #pollInterval}
95322 - {@link Ext.form.field.VTypes}
95323 - {@link Ext.form.Basic#doAction BasicForm.doAction clientValidation notes}
95324
95325 __Form Submission__
95326
95327 By default, Ext Forms are submitted through Ajax, using {@link Ext.form.action.Action}. See the documentation for
95328 {@link Ext.form.Basic} for details.
95329 {@img Ext.form.FormPanel/Ext.form.FormPanel.png Ext.form.FormPanel FormPanel component}
95330 __Example usage:__
95331
95332     Ext.create('Ext.form.Panel', {
95333         title: 'Simple Form',
95334         bodyPadding: 5,
95335         width: 350,
95336         
95337         // The form will submit an AJAX request to this URL when submitted
95338         url: 'save-form.php',
95339         
95340         // Fields will be arranged vertically, stretched to full width
95341         layout: 'anchor',
95342         defaults: {
95343             anchor: '100%'
95344         },
95345         
95346         // The fields
95347         defaultType: 'textfield',
95348         items: [{
95349             fieldLabel: 'First Name',
95350             name: 'first',
95351             allowBlank: false
95352         },{
95353             fieldLabel: 'Last Name',
95354             name: 'last',
95355             allowBlank: false
95356         }],
95357         
95358         // Reset and Submit buttons
95359         buttons: [{
95360             text: 'Reset',
95361             handler: function() {
95362                 this.up('form').getForm().reset();
95363             }
95364         }, {
95365             text: 'Submit',
95366             formBind: true, //only enabled once the form is valid
95367             disabled: true,
95368             handler: function() {
95369                 var form = this.up('form').getForm();
95370                 if (form.isValid()) {
95371                     form.submit({
95372                         success: function(form, action) {
95373                            Ext.Msg.alert('Success', action.result.msg);
95374                         },
95375                         failure: function(form, action) {
95376                             Ext.Msg.alert('Failed', action.result.msg);
95377                         }
95378                     });
95379                 }
95380             }
95381         }],
95382         renderTo: Ext.getBody()
95383     });
95384
95385  * @constructor
95386  * @param {Object} config Configuration options
95387  * @xtype form
95388  *
95389  * @markdown
95390  * @docauthor Jason Johnston <jason@sencha.com>
95391  */
95392 Ext.define('Ext.form.Panel', {
95393     extend:'Ext.panel.Panel',
95394     mixins: {
95395         fieldAncestor: 'Ext.form.FieldAncestor'
95396     },
95397     alias: 'widget.form',
95398     alternateClassName: ['Ext.FormPanel', 'Ext.form.FormPanel'],
95399     requires: ['Ext.form.Basic', 'Ext.util.TaskRunner'],
95400
95401     /**
95402      * @cfg {Boolean} pollForChanges
95403      * If set to <tt>true</tt>, sets up an interval task (using the {@link #pollInterval}) in which the 
95404      * panel's fields are repeatedly checked for changes in their values. This is in addition to the normal detection
95405      * each field does on its own input element, and is not needed in most cases. It does, however, provide a
95406      * means to absolutely guarantee detection of all changes including some edge cases in some browsers which
95407      * do not fire native events. Defaults to <tt>false</tt>.
95408      */
95409
95410     /**
95411      * @cfg {Number} pollInterval
95412      * Interval in milliseconds at which the form's fields are checked for value changes. Only used if
95413      * the {@link #pollForChanges} option is set to <tt>true</tt>. Defaults to 500 milliseconds.
95414      */
95415
95416     /**
95417      * @cfg {String} layout The {@link Ext.container.Container#layout} for the form panel's immediate child items.
95418      * Defaults to <tt>'anchor'</tt>.
95419      */
95420     layout: 'anchor',
95421
95422     ariaRole: 'form',
95423
95424     initComponent: function() {
95425         var me = this;
95426         
95427         if (me.frame) {
95428             me.border = false;
95429         }
95430         
95431         me.initFieldAncestor();
95432         me.callParent();
95433
95434         me.relayEvents(me.form, [
95435             'beforeaction',
95436             'actionfailed',
95437             'actioncomplete',
95438             'validitychange',
95439             'dirtychange'
95440         ]);
95441
95442         // Start polling if configured
95443         if (me.pollForChanges) {
95444             me.startPolling(me.pollInterval || 500);
95445         }
95446     },
95447
95448     initItems: function() {
95449         // Create the BasicForm
95450         var me = this;
95451         
95452         me.form = me.createForm();
95453         me.callParent();
95454         me.form.initialize();
95455     },
95456
95457     /**
95458      * @private
95459      */
95460     createForm: function() {
95461         return Ext.create('Ext.form.Basic', this, Ext.applyIf({listeners: {}}, this.initialConfig));
95462     },
95463
95464     /**
95465      * Provides access to the {@link Ext.form.Basic Form} which this Panel contains.
95466      * @return {Ext.form.Basic} The {@link Ext.form.Basic Form} which this Panel contains.
95467      */
95468     getForm: function() {
95469         return this.form;
95470     },
95471     
95472     /**
95473      * Loads an {@link Ext.data.Model} into this form (internally just calls {@link Ext.form.Basic#loadRecord})
95474      * See also {@link #trackResetOnLoad}.
95475      * @param {Ext.data.Model} record The record to load
95476      * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel
95477      */
95478     loadRecord: function(record) {
95479         return this.getForm().loadRecord(record);
95480     },
95481     
95482     /**
95483      * Returns the currently loaded Ext.data.Model instance if one was loaded via {@link #loadRecord}.
95484      * @return {Ext.data.Model} The loaded instance
95485      */
95486     getRecord: function() {
95487         return this.getForm().getRecord();
95488     },
95489     
95490     /**
95491      * Convenience function for fetching the current value of each field in the form. This is the same as calling
95492      * {@link Ext.form.Basic#getValues this.getForm().getValues()}
95493      * @return {Object} The current form field values, keyed by field name
95494      */
95495     getValues: function() {
95496         return this.getForm().getValues();
95497     },
95498
95499     beforeDestroy: function() {
95500         this.stopPolling();
95501         this.form.destroy();
95502         this.callParent();
95503     },
95504
95505     /**
95506      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#load} call.
95507      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#load} and
95508      * {@link Ext.form.Basic#doAction} for details)
95509      */
95510     load: function(options) {
95511         this.form.load(options);
95512     },
95513
95514     /**
95515      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#submit} call.
95516      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#submit} and
95517      * {@link Ext.form.Basic#doAction} for details)
95518      */
95519     submit: function(options) {
95520         this.form.submit(options);
95521     },
95522
95523     /*
95524      * Inherit docs, not using onDisable because it only gets fired
95525      * when the component is rendered.
95526      */
95527     disable: function(silent) {
95528         this.callParent(arguments);
95529         this.form.getFields().each(function(field) {
95530             field.disable();
95531         });
95532     },
95533
95534     /*
95535      * Inherit docs, not using onEnable because it only gets fired
95536      * when the component is rendered.
95537      */
95538     enable: function(silent) {
95539         this.callParent(arguments);
95540         this.form.getFields().each(function(field) {
95541             field.enable();
95542         });
95543     },
95544
95545     /**
95546      * Start an interval task to continuously poll all the fields in the form for changes in their
95547      * values. This is normally started automatically by setting the {@link #pollForChanges} config.
95548      * @param {Number} interval The interval in milliseconds at which the check should run.
95549      */
95550     startPolling: function(interval) {
95551         this.stopPolling();
95552         var task = Ext.create('Ext.util.TaskRunner', interval);
95553         task.start({
95554             interval: 0,
95555             run: this.checkChange,
95556             scope: this
95557         });
95558         this.pollTask = task;
95559     },
95560
95561     /**
95562      * Stop a running interval task that was started by {@link #startPolling}.
95563      */
95564     stopPolling: function() {
95565         var task = this.pollTask;
95566         if (task) {
95567             task.stopAll();
95568             delete this.pollTask;
95569         }
95570     },
95571
95572     /**
95573      * Forces each field within the form panel to 
95574      * {@link Ext.form.field.Field#checkChange check if its value has changed}.
95575      */
95576     checkChange: function() {
95577         this.form.getFields().each(function(field) {
95578             field.checkChange();
95579         });
95580     }
95581 });
95582
95583 /**
95584  * @class Ext.form.RadioGroup
95585  * @extends Ext.form.CheckboxGroup
95586  * <p>A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
95587  * {@link Ext.form.field.Radio} controls into columns, and provides convenience {@link Ext.form.field.Field} methods
95588  * for {@link #getValue getting}, {@link #setValue setting}, and {@link #validate validating} the group
95589  * of radio buttons as a whole.</p>
95590  * <p><b>Validation:</b> Individual radio buttons themselves have no default validation behavior, but
95591  * sometimes you want to require a user to select one of a group of radios. RadioGroup
95592  * allows this by setting the config <tt>{@link #allowBlank}:false</tt>; when the user does not check at
95593  * one of the radio buttons, the entire group will be highlighted as invalid and the
95594  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.</p>
95595  * <p><b>Layout:</b> The default layout for RadioGroup makes it easy to arrange the radio buttons into
95596  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
95597  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
95598  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
95599  * the Radio components at any depth will still be managed by the RadioGroup's validation.</p>
95600  * <p>Example usage:</p>
95601  * <pre><code>
95602 var myRadioGroup = new Ext.form.RadioGroup({
95603     id: 'myGroup',
95604     xtype: 'radiogroup',
95605     fieldLabel: 'Single Column',
95606     // Arrange radio buttons into three columns, distributed vertically
95607     columns: 3,
95608     vertical: true,
95609     items: [
95610         {boxLabel: 'Item 1', name: 'rb', inputValue: '1'},
95611         {boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},
95612         {boxLabel: 'Item 3', name: 'rb', inputValue: '3'}
95613         {boxLabel: 'Item 4', name: 'rb', inputValue: '4'}
95614         {boxLabel: 'Item 5', name: 'rb', inputValue: '5'}
95615         {boxLabel: 'Item 6', name: 'rb', inputValue: '6'}
95616     ]
95617 });
95618  * </code></pre>
95619  * @constructor
95620  * Creates a new RadioGroup
95621  * @param {Object} config Configuration options
95622  * @xtype radiogroup
95623  */
95624 Ext.define('Ext.form.RadioGroup', {
95625     extend: 'Ext.form.CheckboxGroup',
95626     alias: 'widget.radiogroup',
95627
95628     /**
95629      * @cfg {Array} items An Array of {@link Ext.form.field.Radio Radio}s or Radio config objects
95630      * to arrange in the group.
95631      */
95632     /**
95633      * @cfg {Boolean} allowBlank True to allow every item in the group to be blank (defaults to true).
95634      * If allowBlank = false and no items are selected at validation time, {@link @blankText} will
95635      * be used as the error text.
95636      */
95637     allowBlank : true,
95638     /**
95639      * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
95640      * (defaults to 'You must select one item in this group')
95641      */
95642     blankText : 'You must select one item in this group',
95643     
95644     // private
95645     defaultType : 'radiofield',
95646     
95647     // private
95648     groupCls : Ext.baseCSSPrefix + 'form-radio-group',
95649
95650     getBoxes: function() {
95651         return this.query('[isRadio]');
95652     }
95653
95654 });
95655
95656 /**
95657  * @private
95658  * Private utility class for managing all {@link Ext.form.field.Radio} fields grouped by name.
95659  */
95660 Ext.define('Ext.form.RadioManager', {
95661     extend: 'Ext.util.MixedCollection',
95662     singleton: true,
95663
95664     getByName: function(name) {
95665         return this.filterBy(function(item) {
95666             return item.name == name;
95667         });
95668     },
95669
95670     getWithValue: function(name, value) {
95671         return this.filterBy(function(item) {
95672             return item.name == name && item.inputValue == value;
95673         });
95674     },
95675
95676     getChecked: function(name) {
95677         return this.findBy(function(item) {
95678             return item.name == name && item.checked;
95679         });
95680     }
95681 });
95682
95683 /**
95684  * @class Ext.form.action.DirectLoad
95685  * @extends Ext.form.action.Load
95686  * <p>Provides {@link Ext.direct.Manager} support for loading form data.</p>
95687  * <p>This example illustrates usage of Ext.direct.Direct to <b>load</b> a form through Ext.Direct.</p>
95688  * <pre><code>
95689 var myFormPanel = new Ext.form.Panel({
95690     // configs for FormPanel
95691     title: 'Basic Information',
95692     renderTo: document.body,
95693     width: 300, height: 160,
95694     padding: 10,
95695
95696     // configs apply to child items
95697     defaults: {anchor: '100%'},
95698     defaultType: 'textfield',
95699     items: [{
95700         fieldLabel: 'Name',
95701         name: 'name'
95702     },{
95703         fieldLabel: 'Email',
95704         name: 'email'
95705     },{
95706         fieldLabel: 'Company',
95707         name: 'company'
95708     }],
95709
95710     // configs for BasicForm
95711     api: {
95712         // The server-side method to call for load() requests
95713         load: Profile.getBasicInfo,
95714         // The server-side must mark the submit handler as a 'formHandler'
95715         submit: Profile.updateBasicInfo
95716     },
95717     // specify the order for the passed params
95718     paramOrder: ['uid', 'foo']
95719 });
95720
95721 // load the form
95722 myFormPanel.getForm().load({
95723     // pass 2 arguments to server side getBasicInfo method (len=2)
95724     params: {
95725         foo: 'bar',
95726         uid: 34
95727     }
95728 });
95729  * </code></pre>
95730  * The data packet sent to the server will resemble something like:
95731  * <pre><code>
95732 [
95733     {
95734         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
95735         "data":[34,"bar"] // note the order of the params
95736     }
95737 ]
95738  * </code></pre>
95739  * The form will process a data packet returned by the server that is similar
95740  * to the following format:
95741  * <pre><code>
95742 [
95743     {
95744         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
95745         "result":{
95746             "success":true,
95747             "data":{
95748                 "name":"Fred Flintstone",
95749                 "company":"Slate Rock and Gravel",
95750                 "email":"fred.flintstone@slaterg.com"
95751             }
95752         }
95753     }
95754 ]
95755  * </code></pre>
95756  */
95757 Ext.define('Ext.form.action.DirectLoad', {
95758     extend:'Ext.form.action.Load',
95759     requires: ['Ext.direct.Manager'],
95760     alternateClassName: 'Ext.form.Action.DirectLoad',
95761     alias: 'formaction.directload',
95762
95763     type: 'directload',
95764
95765     run: function() {
95766         this.form.api.load.apply(window, this.getArgs());
95767     },
95768
95769     /**
95770      * @private
95771      * Build the arguments to be sent to the Direct call.
95772      * @return Array
95773      */
95774     getArgs: function() {
95775         var me = this,
95776             args = [],
95777             form = me.form,
95778             paramOrder = form.paramOrder,
95779             params = me.getParams(),
95780             i, len;
95781
95782         // If a paramOrder was specified, add the params into the argument list in that order.
95783         if (paramOrder) {
95784             for (i = 0, len = paramOrder.length; i < len; i++) {
95785                 args.push(params[paramOrder[i]]);
95786             }
95787         }
95788         // If paramsAsHash was specified, add all the params as a single object argument.
95789         else if (form.paramsAsHash) {
95790             args.push(params);
95791         }
95792
95793         // Add the callback and scope to the end of the arguments list
95794         args.push(me.onSuccess, me);
95795
95796         return args;
95797     },
95798
95799     // Direct actions have already been processed and therefore
95800     // we can directly set the result; Direct Actions do not have
95801     // a this.response property.
95802     processResponse: function(result) {
95803         return (this.result = result);
95804     },
95805
95806     onSuccess: function(result, trans) {
95807         if (trans.type == Ext.direct.Manager.self.exceptions.SERVER) {
95808             result = {};
95809         }
95810         this.callParent([result]);
95811     }
95812 });
95813
95814
95815
95816 /**
95817  * @class Ext.form.action.DirectSubmit
95818  * @extends Ext.form.action.Submit
95819  * <p>Provides Ext.direct support for submitting form data.</p>
95820  * <p>This example illustrates usage of Ext.direct.Direct to <b>submit</b> a form through Ext.Direct.</p>
95821  * <pre><code>
95822 var myFormPanel = new Ext.form.Panel({
95823     // configs for FormPanel
95824     title: 'Basic Information',
95825     renderTo: document.body,
95826     width: 300, height: 160,
95827     padding: 10,
95828     buttons:[{
95829         text: 'Submit',
95830         handler: function(){
95831             myFormPanel.getForm().submit({
95832                 params: {
95833                     foo: 'bar',
95834                     uid: 34
95835                 }
95836             });
95837         }
95838     }],
95839
95840     // configs apply to child items
95841     defaults: {anchor: '100%'},
95842     defaultType: 'textfield',
95843     items: [{
95844         fieldLabel: 'Name',
95845         name: 'name'
95846     },{
95847         fieldLabel: 'Email',
95848         name: 'email'
95849     },{
95850         fieldLabel: 'Company',
95851         name: 'company'
95852     }],
95853
95854     // configs for BasicForm
95855     api: {
95856         // The server-side method to call for load() requests
95857         load: Profile.getBasicInfo,
95858         // The server-side must mark the submit handler as a 'formHandler'
95859         submit: Profile.updateBasicInfo
95860     },
95861     // specify the order for the passed params
95862     paramOrder: ['uid', 'foo']
95863 });
95864  * </code></pre>
95865  * The data packet sent to the server will resemble something like:
95866  * <pre><code>
95867 {
95868     "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
95869     "result":{
95870         "success":true,
95871         "id":{
95872             "extAction":"Profile","extMethod":"updateBasicInfo",
95873             "extType":"rpc","extTID":"6","extUpload":"false",
95874             "name":"Aaron Conran","email":"aaron@sencha.com","company":"Sencha Inc."
95875         }
95876     }
95877 }
95878  * </code></pre>
95879  * The form will process a data packet returned by the server that is similar
95880  * to the following:
95881  * <pre><code>
95882 // sample success packet (batched requests)
95883 [
95884     {
95885         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
95886         "result":{
95887             "success":true
95888         }
95889     }
95890 ]
95891
95892 // sample failure packet (one request)
95893 {
95894         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
95895         "result":{
95896             "errors":{
95897                 "email":"already taken"
95898             },
95899             "success":false,
95900             "foo":"bar"
95901         }
95902 }
95903  * </code></pre>
95904  * Also see the discussion in {@link Ext.form.action.DirectLoad}.
95905  */
95906 Ext.define('Ext.form.action.DirectSubmit', {
95907     extend:'Ext.form.action.Submit',
95908     requires: ['Ext.direct.Manager'],
95909     alternateClassName: 'Ext.form.Action.DirectSubmit',
95910     alias: 'formaction.directsubmit',
95911
95912     type: 'directsubmit',
95913
95914     doSubmit: function() {
95915         var me = this,
95916             callback = Ext.Function.bind(me.onSuccess, me),
95917             formEl = me.buildForm();
95918         me.form.api.submit(formEl, callback, me);
95919         Ext.removeNode(formEl);
95920     },
95921
95922     // Direct actions have already been processed and therefore
95923     // we can directly set the result; Direct Actions do not have
95924     // a this.response property.
95925     processResponse: function(result) {
95926         return (this.result = result);
95927     },
95928
95929     onSuccess: function(response, trans) {
95930         if (trans.type === Ext.direct.Manager.self.exceptions.SERVER) {
95931             response = {};
95932         }
95933         this.callParent([response]);
95934     }
95935 });
95936
95937 /**
95938  * @class Ext.form.action.StandardSubmit
95939  * @extends Ext.form.action.Submit
95940  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s using a standard
95941  * <tt>&lt;form&gt;</tt> element submit. It does not handle the response from the submit.</p>
95942  * <p>If validation of the form fields fails, the Form's {@link Ext.form.Basic#afterAction} method
95943  * will be called. Otherwise, afterAction will not be called.</p>
95944  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
95945  * {@link Ext.form.Basic#submit submit}ting, when the form's {@link Ext.form.Basic#standardSubmit}
95946  * config option is <tt>true</tt>.</p>
95947  */
95948 Ext.define('Ext.form.action.StandardSubmit', {
95949     extend:'Ext.form.action.Submit',
95950     alias: 'formaction.standardsubmit',
95951
95952     /**
95953      * @cfg {String} target
95954      * Optional <tt>target</tt> attribute to be used for the form when submitting. If not specified,
95955      * the target will be the current window/frame.
95956      */
95957
95958     /**
95959      * @private
95960      * Perform the form submit. Creates and submits a temporary form element containing an input element for each
95961      * field value returned by {@link Ext.form.Basic#getValues}, plus any configured {@link #params params} or
95962      * {@link Ext.form.Basic#baseParams baseParams}.
95963      */
95964     doSubmit: function() {
95965         var form = this.buildForm();
95966         form.submit();
95967         Ext.removeNode(form);
95968     }
95969
95970 });
95971
95972 /**
95973  * @class Ext.form.field.Checkbox
95974  * @extends Ext.form.field.Base
95975
95976 Single checkbox field. Can be used as a direct replacement for traditional checkbox fields. Also serves as a
95977 parent class for {@link Ext.form.field.Radio radio buttons}.
95978
95979 __Labeling:__ In addition to the {@link Ext.form.Labelable standard field labeling options}, checkboxes
95980 may be given an optional {@link #boxLabel} which will be displayed immediately after checkbox. Also see
95981 {@link Ext.form.CheckboxGroup} for a convenient method of grouping related checkboxes.
95982
95983 __Values:__
95984 The main value of a checkbox is a boolean, indicating whether or not the checkbox is checked.
95985 The following values will check the checkbox:
95986 * `true`
95987 * `'true'`
95988 * `'1'`
95989 * `'on'`
95990
95991 Any other value will uncheck the checkbox.
95992
95993 In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be
95994 sent as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set
95995 this value if you have multiple checkboxes with the same {@link #name}. If not specified, the value `on`
95996 will be used.
95997 {@img Ext.form.Checkbox/Ext.form.Checkbox.png Ext.form.Checkbox Checkbox component}
95998 __Example usage:__
95999
96000     Ext.create('Ext.form.Panel', {
96001         bodyPadding: 10,
96002         width      : 300,
96003         title      : 'Pizza Order',
96004         items: [
96005             {
96006                 xtype      : 'fieldcontainer',
96007                 fieldLabel : 'Toppings',
96008                 defaultType: 'checkboxfield',
96009                 items: [
96010                     {
96011                         boxLabel  : 'Anchovies',
96012                         name      : 'topping',
96013                         inputValue: '1',
96014                         id        : 'checkbox1'
96015                     }, {
96016                         boxLabel  : 'Artichoke Hearts',
96017                         name      : 'topping',
96018                         inputValue: '2',
96019                         checked   : true,
96020                         id        : 'checkbox2'
96021                     }, {
96022                         boxLabel  : 'Bacon',
96023                         name      : 'topping',
96024                         inputValue: '3',
96025                         id        : 'checkbox3'
96026                     }
96027                 ]
96028             }
96029         ],
96030         bbar: [
96031             {
96032                 text: 'Select Bacon',
96033                 handler: function() {
96034                     var checkbox = Ext.getCmp('checkbox3');
96035                     checkbox.setValue(true);
96036                 }
96037             },
96038             '-',
96039             {
96040                 text: 'Select All',
96041                 handler: function() {
96042                     var checkbox1 = Ext.getCmp('checkbox1'),
96043                         checkbox2 = Ext.getCmp('checkbox2'),
96044                         checkbox3 = Ext.getCmp('checkbox3');
96045
96046                     checkbox1.setValue(true);
96047                     checkbox2.setValue(true);
96048                     checkbox3.setValue(true);
96049                 }
96050             },
96051             {
96052                 text: 'Deselect All',
96053                 handler: function() {
96054                     var checkbox1 = Ext.getCmp('checkbox1'),
96055                         checkbox2 = Ext.getCmp('checkbox2'),
96056                         checkbox3 = Ext.getCmp('checkbox3');
96057
96058                     checkbox1.setValue(false);
96059                     checkbox2.setValue(false);
96060                     checkbox3.setValue(false);
96061                 }
96062             }
96063         ],
96064         renderTo: Ext.getBody()
96065     });
96066
96067  * @constructor
96068  * Creates a new Checkbox
96069  * @param {Object} config Configuration options
96070  * @xtype checkboxfield
96071  * @docauthor Robert Dougan <rob@sencha.com>
96072  * @markdown
96073  */
96074 Ext.define('Ext.form.field.Checkbox', {
96075     extend: 'Ext.form.field.Base',
96076     alias: ['widget.checkboxfield', 'widget.checkbox'],
96077     alternateClassName: 'Ext.form.Checkbox',
96078     requires: ['Ext.XTemplate', 'Ext.form.CheckboxManager'],
96079
96080     fieldSubTpl: [
96081         '<tpl if="boxLabel && boxLabelAlign == \'before\'">',
96082             '<label class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
96083         '</tpl>',
96084         // Creates not an actual checkbox, but a button which is given aria role="checkbox" and
96085         // styled with a custom checkbox image. This allows greater control and consistency in
96086         // styling, and using a button allows it to gain focus and handle keyboard nav properly.
96087         '<input type="button" id="{id}" ',
96088             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
96089             'class="{fieldCls} {typeCls}" autocomplete="off" hidefocus="true" />',
96090         '<tpl if="boxLabel && boxLabelAlign == \'after\'">',
96091             '<label class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
96092         '</tpl>',
96093         {
96094             disableFormats: true,
96095             compiled: true
96096         }
96097     ],
96098
96099     isCheckbox: true,
96100
96101     /**
96102      * @cfg {String} focusCls The CSS class to use when the checkbox receives focus
96103      * (defaults to <tt>'x-form-cb-focus'</tt>)
96104      */
96105     focusCls: Ext.baseCSSPrefix + 'form-cb-focus',
96106
96107     /**
96108      * @cfg {String} fieldCls The default CSS class for the checkbox (defaults to <tt>'x-form-field'</tt>)
96109      */
96110
96111     /**
96112      * @cfg {String} fieldBodyCls
96113      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
96114      * Defaults to 'x-form-cb-wrap.
96115      */
96116     fieldBodyCls: Ext.baseCSSPrefix + 'form-cb-wrap',
96117
96118     /**
96119      * @cfg {Boolean} checked <tt>true</tt> if the checkbox should render initially checked (defaults to <tt>false</tt>)
96120      */
96121     checked: false,
96122
96123     /**
96124      * @cfg {String} checkedCls The CSS class added to the component's main element when it is in the checked state.
96125      */
96126     checkedCls: Ext.baseCSSPrefix + 'form-cb-checked',
96127
96128     /**
96129      * @cfg {String} boxLabel An optional text label that will appear next to the checkbox. Whether it appears before
96130      * or after the checkbox is determined by the {@link #boxLabelAlign} config (defaults to after).
96131      */
96132
96133     /**
96134      * @cfg {String} boxLabelCls The CSS class to be applied to the {@link #boxLabel} element
96135      */
96136     boxLabelCls: Ext.baseCSSPrefix + 'form-cb-label',
96137
96138     /**
96139      * @cfg {String} boxLabelAlign The position relative to the checkbox where the {@link #boxLabel} should
96140      * appear. Recognized values are <tt>'before'</tt> and <tt>'after'</tt>. Defaults to <tt>'after'</tt>.
96141      */
96142     boxLabelAlign: 'after',
96143
96144     /**
96145      * @cfg {String} inputValue The value that should go into the generated input element's value attribute and
96146      * should be used as the parameter value when submitting as part of a form. Defaults to <tt>"on"</tt>.
96147      */
96148     inputValue: 'on',
96149
96150     /**
96151      * @cfg {String} uncheckedValue If configured, this will be submitted as the checkbox's value during form
96152      * submit if the checkbox is unchecked. By default this is undefined, which results in nothing being
96153      * submitted for the checkbox field when the form is submitted (the default behavior of HTML checkboxes).
96154      */
96155
96156     /**
96157      * @cfg {Function} handler A function called when the {@link #checked} value changes (can be used instead of
96158      * handling the {@link #change change event}). The handler is passed the following parameters:
96159      * <div class="mdetail-params"><ul>
96160      * <li><b>checkbox</b> : Ext.form.field.Checkbox<div class="sub-desc">The Checkbox being toggled.</div></li>
96161      * <li><b>checked</b> : Boolean<div class="sub-desc">The new checked state of the checkbox.</div></li>
96162      * </ul></div>
96163      */
96164
96165     /**
96166      * @cfg {Object} scope An object to use as the scope ('this' reference) of the {@link #handler} function
96167      * (defaults to this Checkbox).
96168      */
96169
96170     // private overrides
96171     checkChangeEvents: [],
96172     inputType: 'checkbox',
96173     ariaRole: 'checkbox',
96174
96175     // private
96176     onRe: /^on$/i,
96177
96178     initComponent: function(){
96179         this.callParent(arguments);
96180         this.getManager().add(this);
96181     },
96182
96183     initValue: function() {
96184         var me = this,
96185             checked = !!me.checked;
96186
96187         /**
96188          * The original value of the field as configured in the {@link #checked} configuration, or
96189          * as loaded by the last form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
96190          * setting is <code>true</code>.
96191          * @type Mixed
96192          * @property originalValue
96193          */
96194         me.originalValue = me.lastValue = checked;
96195
96196         // Set the initial checked state
96197         me.setValue(checked);
96198     },
96199
96200     // private
96201     onRender : function(ct, position) {
96202         var me = this;
96203         Ext.applyIf(me.renderSelectors, {
96204             /**
96205              * @property boxLabelEl
96206              * @type Ext.core.Element
96207              * A reference to the label element created for the {@link #boxLabel}. Only present if the
96208              * component has been rendered and has a boxLabel configured.
96209              */
96210             boxLabelEl: 'label.' + me.boxLabelCls
96211         });
96212         Ext.applyIf(me.subTplData, {
96213             boxLabel: me.boxLabel,
96214             boxLabelCls: me.boxLabelCls,
96215             boxLabelAlign: me.boxLabelAlign
96216         });
96217
96218         me.callParent(arguments);
96219     },
96220
96221     initEvents: function() {
96222         var me = this;
96223         me.callParent();
96224         me.mon(me.inputEl, 'click', me.onBoxClick, me);
96225     },
96226
96227     /**
96228      * @private Handle click on the checkbox button
96229      */
96230     onBoxClick: function(e) {
96231         var me = this;
96232         if (!me.disabled && !me.readOnly) {
96233             this.setValue(!this.checked);
96234         }
96235     },
96236
96237     /**
96238      * Returns the checked state of the checkbox.
96239      * @return {Boolean} True if checked, else false
96240      */
96241     getRawValue: function() {
96242         return this.checked;
96243     },
96244
96245     /**
96246      * Returns the checked state of the checkbox.
96247      * @return {Boolean} True if checked, else false
96248      */
96249     getValue: function() {
96250         return this.checked;
96251     },
96252
96253     /**
96254      * Returns the submit value for the checkbox which can be used when submitting forms.
96255      * @return {Boolean/null} True if checked; otherwise either the {@link #uncheckedValue} or null.
96256      */
96257     getSubmitValue: function() {
96258         var unchecked = this.uncheckedValue,
96259             uncheckedVal = Ext.isDefined(unchecked) ? unchecked : null;
96260         return this.checked ? this.inputValue : uncheckedVal;
96261     },
96262
96263     /**
96264      * Sets the checked state of the checkbox.
96265      * @param {Boolean/String} value The following values will check the checkbox:
96266      * <code>true, 'true', '1', or 'on'</code>, as well as a String that matches the {@link #inputValue}.
96267      * Any other value will uncheck the checkbox.
96268      * @return {Boolean} the new checked state of the checkbox
96269      */
96270     setRawValue: function(value) {
96271         var me = this,
96272             inputEl = me.inputEl,
96273             inputValue = me.inputValue,
96274             checked = (value === true || value === 'true' || value === '1' ||
96275                       ((Ext.isString(value) && inputValue) ? value == inputValue : me.onRe.test(value)));
96276
96277         if (inputEl) {
96278             inputEl.dom.setAttribute('aria-checked', checked);
96279             me[checked ? 'addCls' : 'removeCls'](me.checkedCls);
96280         }
96281
96282         me.checked = me.rawValue = checked;
96283         return checked;
96284     },
96285
96286     /**
96287      * Sets the checked state of the checkbox, and invokes change detection.
96288      * @param {Boolean/String} checked The following values will check the checkbox:
96289      * <code>true, 'true', '1', or 'on'</code>, as well as a String that matches the {@link #inputValue}.
96290      * Any other value will uncheck the checkbox.
96291      * @return {Ext.form.field.Checkbox} this
96292      */
96293     setValue: function(checked) {
96294         var me = this;
96295
96296         // If an array of strings is passed, find all checkboxes in the group with the same name as this
96297         // one and check all those whose inputValue is in the array, unchecking all the others. This is to
96298         // facilitate setting values from Ext.form.Basic#setValues, but is not publicly documented as we
96299         // don't want users depending on this behavior.
96300         if (Ext.isArray(checked)) {
96301             me.getManager().getByName(me.name).each(function(cb) {
96302                 cb.setValue(Ext.Array.contains(checked, cb.inputValue));
96303             });
96304         } else {
96305             me.callParent(arguments);
96306         }
96307
96308         return me;
96309     },
96310
96311     // private
96312     valueToRaw: function(value) {
96313         // No extra conversion for checkboxes
96314         return value;
96315     },
96316
96317     /**
96318      * @private
96319      * Called when the checkbox's checked state changes. Invokes the {@link #handler} callback
96320      * function if specified.
96321      */
96322     onChange: function(newVal, oldVal) {
96323         var me = this,
96324             handler = me.handler;
96325         if (handler) {
96326             handler.call(me.scope || me, me, newVal);
96327         }
96328         me.callParent(arguments);
96329     },
96330
96331     // inherit docs
96332     getManager: function() {
96333         return Ext.form.CheckboxManager;
96334     },
96335
96336     onEnable: function() {
96337         var me = this,
96338             inputEl = me.inputEl;
96339         me.callParent();
96340         if (inputEl) {
96341             // Can still be disabled if the field is readOnly
96342             inputEl.dom.disabled = me.readOnly;
96343         }
96344     },
96345
96346     setReadOnly: function(readOnly) {
96347         var me = this,
96348             inputEl = me.inputEl;
96349         if (inputEl) {
96350             // Set the button to disabled when readonly
96351             inputEl.dom.disabled = readOnly || me.disabled;
96352         }
96353         me.readOnly = readOnly;
96354     },
96355
96356     /**
96357      * @protected Calculate and return the natural width of the bodyEl. It's possible that the initial
96358      * rendering will cause the boxLabel to wrap and give us a bad width, so we must prevent wrapping
96359      * while measuring.
96360      */
96361     getBodyNaturalWidth: function() {
96362         var me = this,
96363             bodyEl = me.bodyEl,
96364             ws = 'white-space',
96365             width;
96366         bodyEl.setStyle(ws, 'nowrap');
96367         width = bodyEl.getWidth();
96368         bodyEl.setStyle(ws, '');
96369         return width;
96370     }
96371
96372 });
96373
96374 /**
96375  * @private
96376  * @class Ext.layout.component.field.Trigger
96377  * @extends Ext.layout.component.field.Field
96378  * Layout class for {@link Ext.form.field.Trigger} fields. Adjusts the input field size to accommodate
96379  * the trigger button(s).
96380  * @private
96381  */
96382
96383 Ext.define('Ext.layout.component.field.Trigger', {
96384
96385     /* Begin Definitions */
96386
96387     alias: ['layout.triggerfield'],
96388
96389     extend: 'Ext.layout.component.field.Field',
96390
96391     /* End Definitions */
96392
96393     type: 'triggerfield',
96394
96395     sizeBodyContents: function(width, height) {
96396         var me = this,
96397             owner = me.owner,
96398             inputEl = owner.inputEl,
96399             triggerWrap = owner.triggerWrap,
96400             triggerWidth = owner.getTriggerWidth();
96401
96402         // If we or our ancestor is hidden, we can get a triggerWidth calculation
96403         // of 0.  We don't want to resize in this case.
96404         if (owner.hideTrigger || owner.readOnly || triggerWidth > 0) {
96405             // Decrease the field's width by the width of the triggers. Both the field and the triggerWrap
96406             // are floated left in CSS so they'll stack up side by side.
96407             me.setElementSize(inputEl, Ext.isNumber(width) ? width - triggerWidth : width);
96408     
96409             // Explicitly set the triggerWrap's width, to prevent wrapping
96410             triggerWrap.setWidth(triggerWidth);
96411         }
96412     }
96413 });
96414 /**
96415  * @class Ext.view.View
96416  * @extends Ext.view.AbstractView
96417  *
96418  * A mechanism for displaying data using custom layout templates and formatting. DataView uses an {@link Ext.XTemplate}
96419  * as its internal templating mechanism, and is bound to an {@link Ext.data.Store}
96420  * so that as the data in the store changes the view is automatically updated to reflect the changes.  The view also
96421  * provides built-in behavior for many common events that can occur for its contained items including click, doubleclick,
96422  * mouseover, mouseout, etc. as well as a built-in selection model. <b>In order to use these features, an {@link #itemSelector}
96423  * config must be provided for the DataView to determine what nodes it will be working with.</b>
96424  *
96425  * The example below binds a DataView to a {@link Ext.data.Store} and renders it into an {@link Ext.panel.Panel}.
96426  *
96427  * {@img Ext.DataView/Ext.DataView.png Ext.DataView component}
96428  *
96429  *     Ext.regModel('Image', {
96430  *         Fields: [
96431  *             {name:'src', type:'string'},
96432  *             {name:'caption', type:'string'}
96433  *         ]
96434  *     });
96435  *     
96436  *     Ext.create('Ext.data.Store', {
96437  *         id:'imagesStore',
96438  *         model: 'Image',
96439  *         data: [
96440  *             {src:'http://www.sencha.com/img/20110215-feat-drawing.png', caption:'Drawing & Charts'},
96441  *             {src:'http://www.sencha.com/img/20110215-feat-data.png', caption:'Advanced Data'},
96442  *             {src:'http://www.sencha.com/img/20110215-feat-html5.png', caption:'Overhauled Theme'},
96443  *             {src:'http://www.sencha.com/img/20110215-feat-perf.png', caption:'Performance Tuned'}            
96444  *         ]
96445  *     });
96446  *     
96447  *     var imageTpl = new Ext.XTemplate(
96448  *         '&lt;tpl for="."&gt;',
96449  *             '&lt;div style="thumb-wrap"&gt;',
96450  *               '&lt;img src="{src}" /&gt;',
96451  *               '&lt;br/&gt;&lt;span&gt;{caption}&lt;/span&gt;',
96452  *             '&lt;/div&gt;',
96453  *         '&lt;/tpl&gt;'
96454  *     );
96455  *     
96456  *     Ext.create('Ext.DataView', {
96457  *         store: Ext.data.StoreManager.lookup('imagesStore'),
96458  *         tpl: imageTpl,
96459  *         itemSelector: 'div.thumb-wrap',
96460  *         emptyText: 'No images available',
96461  *         renderTo: Ext.getBody()
96462  *     });
96463  *
96464  * @xtype dataview
96465  */
96466 Ext.define('Ext.view.View', {
96467     extend: 'Ext.view.AbstractView',
96468     alternateClassName: 'Ext.view.View',
96469     alias: 'widget.dataview',
96470
96471     inheritableStatics: {
96472         EventMap: {
96473             mousedown: 'MouseDown',
96474             mouseup: 'MouseUp',
96475             click: 'Click',
96476             dblclick: 'DblClick',
96477             contextmenu: 'ContextMenu',
96478             mouseover: 'MouseOver',
96479             mouseout: 'MouseOut',
96480             mouseenter: 'MouseEnter',
96481             mouseleave: 'MouseLeave',
96482             keydown: 'KeyDown'
96483         }
96484     },
96485
96486     addCmpEvents: function() {
96487         this.addEvents(
96488             /**
96489              * @event beforeitemmousedown
96490              * Fires before the mousedown event on an item is processed. Returns false to cancel the default action.
96491              * @param {Ext.view.View} this
96492              * @param {Ext.data.Model} record The record that belongs to the item
96493              * @param {HTMLElement} item The item's element
96494              * @param {Number} index The item's index
96495              * @param {Ext.EventObject} e The raw event object
96496              */
96497             'beforeitemmousedown',
96498             /**
96499              * @event beforeitemmouseup
96500              * Fires before the mouseup event on an item is processed. Returns false to cancel the default action.
96501              * @param {Ext.view.View} this
96502              * @param {Ext.data.Model} record The record that belongs to the item
96503              * @param {HTMLElement} item The item's element
96504              * @param {Number} index The item's index
96505              * @param {Ext.EventObject} e The raw event object
96506              */
96507             'beforeitemmouseup',
96508             /**
96509              * @event beforeitemmouseenter
96510              * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action.
96511              * @param {Ext.view.View} this
96512              * @param {Ext.data.Model} record The record that belongs to the item
96513              * @param {HTMLElement} item The item's element
96514              * @param {Number} index The item's index
96515              * @param {Ext.EventObject} e The raw event object
96516              */
96517             'beforeitemmouseenter',
96518             /**
96519              * @event beforeitemmouseleave
96520              * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action.
96521              * @param {Ext.view.View} this
96522              * @param {Ext.data.Model} record The record that belongs to the item
96523              * @param {HTMLElement} item The item's element
96524              * @param {Number} index The item's index
96525              * @param {Ext.EventObject} e The raw event object
96526              */
96527             'beforeitemmouseleave',
96528             /**
96529              * @event beforeitemclick
96530              * Fires before the click event on an item is processed. Returns false to cancel the default action.
96531              * @param {Ext.view.View} this
96532              * @param {Ext.data.Model} record The record that belongs to the item
96533              * @param {HTMLElement} item The item's element
96534              * @param {Number} index The item's index
96535              * @param {Ext.EventObject} e The raw event object
96536              */
96537             'beforeitemclick',
96538             /**
96539              * @event beforeitemdblclick
96540              * Fires before the dblclick event on an item is processed. Returns false to cancel the default action.
96541              * @param {Ext.view.View} this
96542              * @param {Ext.data.Model} record The record that belongs to the item
96543              * @param {HTMLElement} item The item's element
96544              * @param {Number} index The item's index
96545              * @param {Ext.EventObject} e The raw event object
96546              */
96547             'beforeitemdblclick',
96548             /**
96549              * @event beforeitemcontextmenu
96550              * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action.
96551              * @param {Ext.view.View} this
96552              * @param {Ext.data.Model} record The record that belongs to the item
96553              * @param {HTMLElement} item The item's element
96554              * @param {Number} index The item's index
96555              * @param {Ext.EventObject} e The raw event object
96556              */
96557             'beforeitemcontextmenu',
96558             /**
96559              * @event beforeitemkeydown
96560              * Fires before the keydown event on an item is processed. Returns false to cancel the default action.
96561              * @param {Ext.view.View} this
96562              * @param {Ext.data.Model} record The record that belongs to the item
96563              * @param {HTMLElement} item The item's element
96564              * @param {Number} index The item's index
96565              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
96566              */
96567             'beforeitemkeydown',
96568             /**
96569              * @event itemmousedown
96570              * Fires when there is a mouse down on an item
96571              * @param {Ext.view.View} this
96572              * @param {Ext.data.Model} record The record that belongs to the item
96573              * @param {HTMLElement} item The item's element
96574              * @param {Number} index The item's index
96575              * @param {Ext.EventObject} e The raw event object
96576              */
96577             'itemmousedown',
96578             /**
96579              * @event itemmouseup
96580              * Fires when there is a mouse up on an item
96581              * @param {Ext.view.View} this
96582              * @param {Ext.data.Model} record The record that belongs to the item
96583              * @param {HTMLElement} item The item's element
96584              * @param {Number} index The item's index
96585              * @param {Ext.EventObject} e The raw event object
96586              */
96587             'itemmouseup',
96588             /**
96589              * @event itemmouseenter
96590              * Fires when the mouse enters an item.
96591              * @param {Ext.view.View} this
96592              * @param {Ext.data.Model} record The record that belongs to the item
96593              * @param {HTMLElement} item The item's element
96594              * @param {Number} index The item's index
96595              * @param {Ext.EventObject} e The raw event object
96596              */
96597             'itemmouseenter',
96598             /**
96599              * @event itemmouseleave
96600              * Fires when the mouse leaves an item.
96601              * @param {Ext.view.View} this
96602              * @param {Ext.data.Model} record The record that belongs to the item
96603              * @param {HTMLElement} item The item's element
96604              * @param {Number} index The item's index
96605              * @param {Ext.EventObject} e The raw event object
96606              */
96607             'itemmouseleave',
96608             /**
96609              * @event itemclick
96610              * Fires when an item is clicked.
96611              * @param {Ext.view.View} this
96612              * @param {Ext.data.Model} record The record that belongs to the item
96613              * @param {HTMLElement} item The item's element
96614              * @param {Number} index The item's index
96615              * @param {Ext.EventObject} e The raw event object
96616              */
96617             'itemclick',
96618             /**
96619              * @event itemdblclick
96620              * Fires when an item is double clicked.
96621              * @param {Ext.view.View} this
96622              * @param {Ext.data.Model} record The record that belongs to the item
96623              * @param {HTMLElement} item The item's element
96624              * @param {Number} index The item's index
96625              * @param {Ext.EventObject} e The raw event object
96626              */
96627             'itemdblclick',
96628             /**
96629              * @event itemcontextmenu
96630              * Fires when an item is right clicked.
96631              * @param {Ext.view.View} this
96632              * @param {Ext.data.Model} record The record that belongs to the item
96633              * @param {HTMLElement} item The item's element
96634              * @param {Number} index The item's index
96635              * @param {Ext.EventObject} e The raw event object
96636              */
96637             'itemcontextmenu',
96638             /**
96639              * @event itemkeydown
96640              * Fires when a key is pressed while an item is currently selected.
96641              * @param {Ext.view.View} this
96642              * @param {Ext.data.Model} record The record that belongs to the item
96643              * @param {HTMLElement} item The item's element
96644              * @param {Number} index The item's index
96645              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
96646              */
96647             'itemkeydown',
96648             /**
96649              * @event beforecontainermousedown
96650              * Fires before the mousedown event on the container is processed. Returns false to cancel the default action.
96651              * @param {Ext.view.View} this
96652              * @param {Ext.EventObject} e The raw event object
96653              */
96654             'beforecontainermousedown',
96655             /**
96656              * @event beforecontainermouseup
96657              * Fires before the mouseup event on the container is processed. Returns false to cancel the default action.
96658              * @param {Ext.view.View} this
96659              * @param {Ext.EventObject} e The raw event object
96660              */
96661             'beforecontainermouseup',
96662             /**
96663              * @event beforecontainermouseover
96664              * Fires before the mouseover event on the container is processed. Returns false to cancel the default action.
96665              * @param {Ext.view.View} this
96666              * @param {Ext.EventObject} e The raw event object
96667              */
96668             'beforecontainermouseover',
96669             /**
96670              * @event beforecontainermouseout
96671              * Fires before the mouseout event on the container is processed. Returns false to cancel the default action.
96672              * @param {Ext.view.View} this
96673              * @param {Ext.EventObject} e The raw event object
96674              */
96675             'beforecontainermouseout',
96676             /**
96677              * @event beforecontainerclick
96678              * Fires before the click event on the container is processed. Returns false to cancel the default action.
96679              * @param {Ext.view.View} this
96680              * @param {Ext.EventObject} e The raw event object
96681              */
96682             'beforecontainerclick',
96683             /**
96684              * @event beforecontainerdblclick
96685              * Fires before the dblclick event on the container is processed. Returns false to cancel the default action.
96686              * @param {Ext.view.View} this
96687              * @param {Ext.EventObject} e The raw event object
96688              */
96689             'beforecontainerdblclick',
96690             /**
96691              * @event beforecontainercontextmenu
96692              * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action.
96693              * @param {Ext.view.View} this
96694              * @param {Ext.EventObject} e The raw event object
96695              */
96696             'beforecontainercontextmenu',
96697             /**
96698              * @event beforecontainerkeydown
96699              * Fires before the keydown event on the container is processed. Returns false to cancel the default action.
96700              * @param {Ext.view.View} this
96701              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
96702              */
96703             'beforecontainerkeydown',
96704             /**
96705              * @event containermouseup
96706              * Fires when there is a mouse up on the container
96707              * @param {Ext.view.View} this
96708              * @param {Ext.EventObject} e The raw event object
96709              */
96710             'containermouseup',
96711             /**
96712              * @event containermouseover
96713              * Fires when you move the mouse over the container.
96714              * @param {Ext.view.View} this
96715              * @param {Ext.EventObject} e The raw event object
96716              */
96717             'containermouseover',
96718             /**
96719              * @event containermouseout
96720              * Fires when you move the mouse out of the container.
96721              * @param {Ext.view.View} this
96722              * @param {Ext.EventObject} e The raw event object
96723              */
96724             'containermouseout',
96725             /**
96726              * @event containerclick
96727              * Fires when the container is clicked.
96728              * @param {Ext.view.View} this
96729              * @param {Ext.EventObject} e The raw event object
96730              */
96731             'containerclick',
96732             /**
96733              * @event containerdblclick
96734              * Fires when the container is double clicked.
96735              * @param {Ext.view.View} this
96736              * @param {Ext.EventObject} e The raw event object
96737              */
96738             'containerdblclick',
96739             /**
96740              * @event containercontextmenu
96741              * Fires when the container is right clicked.
96742              * @param {Ext.view.View} this
96743              * @param {Ext.EventObject} e The raw event object
96744              */
96745             'containercontextmenu',
96746             /**
96747              * @event containerkeydown
96748              * Fires when a key is pressed while the container is focused, and no item is currently selected.
96749              * @param {Ext.view.View} this
96750              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
96751              */
96752             'containerkeydown',
96753
96754             /**
96755              * @event selectionchange
96756              * Fires when the selected nodes change. Relayed event from the underlying selection model.
96757              * @param {Ext.view.View} this
96758              * @param {Array} selections Array of the selected nodes
96759              */
96760             'selectionchange',
96761             /**
96762              * @event beforeselect
96763              * Fires before a selection is made. If any handlers return false, the selection is cancelled.
96764              * @param {Ext.view.View} this
96765              * @param {HTMLElement} node The node to be selected
96766              * @param {Array} selections Array of currently selected nodes
96767              */
96768             'beforeselect'
96769         );
96770     },
96771     // private
96772     afterRender: function(){
96773         var me = this,
96774             listeners;
96775
96776         me.callParent();
96777
96778         listeners = {
96779             scope: me,
96780             click: me.handleEvent,
96781             mousedown: me.handleEvent,
96782             mouseup: me.handleEvent,
96783             dblclick: me.handleEvent,
96784             contextmenu: me.handleEvent,
96785             mouseover: me.handleEvent,
96786             mouseout: me.handleEvent,
96787             keydown: me.handleEvent
96788         };
96789
96790         me.mon(me.getTargetEl(), listeners);
96791
96792         if (me.store) {
96793             me.bindStore(me.store, true);
96794         }
96795     },
96796
96797     handleEvent: function(e) {
96798         if (this.processUIEvent(e) !== false) {
96799             this.processSpecialEvent(e);
96800         }
96801     },
96802
96803     // Private template method
96804     processItemEvent: Ext.emptyFn,
96805     processContainerEvent: Ext.emptyFn,
96806     processSpecialEvent: Ext.emptyFn,
96807
96808     /*
96809      * Returns true if this mouseover/out event is still over the overItem.
96810      */
96811     stillOverItem: function (event, overItem) {
96812         var nowOver;
96813
96814         // There is this weird bug when you hover over the border of a cell it is saying
96815         // the target is the table.
96816         // BrowserBug: IE6 & 7. If me.mouseOverItem has been removed and is no longer
96817         // in the DOM then accessing .offsetParent will throw an "Unspecified error." exception.
96818         // typeof'ng and checking to make sure the offsetParent is an object will NOT throw
96819         // this hard exception.
96820         if (overItem && typeof(overItem.offsetParent) === "object") {
96821             // mouseout : relatedTarget == nowOver, target == wasOver
96822             // mouseover: relatedTarget == wasOver, target == nowOver
96823             nowOver = (event.type == 'mouseout') ? event.getRelatedTarget() : event.getTarget();
96824             return Ext.fly(overItem).contains(nowOver);
96825         }
96826
96827         return false;
96828     },
96829
96830     processUIEvent: function(e) {
96831         var me = this,
96832             item = e.getTarget(me.getItemSelector(), me.getTargetEl()),
96833             map = this.statics().EventMap,
96834             index, record,
96835             type = e.type,
96836             overItem = me.mouseOverItem,
96837             newType;
96838
96839         if (!item) {
96840             if (type == 'mouseover' && me.stillOverItem(e, overItem)) {
96841                 item = overItem;
96842             }
96843
96844             // Try to get the selected item to handle the keydown event, otherwise we'll just fire a container keydown event
96845             if (type == 'keydown') {
96846                 record = me.getSelectionModel().getLastSelected();
96847                 if (record) {
96848                     item = me.getNode(record);
96849                 }
96850             }
96851         }
96852
96853         if (item) {
96854             index = me.indexOf(item);
96855             if (!record) {
96856                 record = me.getRecord(item);
96857             }
96858
96859             if (me.processItemEvent(record, item, index, e) === false) {
96860                 return false;
96861             }
96862
96863             newType = me.isNewItemEvent(item, e);
96864             if (newType === false) {
96865                 return false;
96866             }
96867
96868             if (
96869                 (me['onBeforeItem' + map[newType]](record, item, index, e) === false) ||
96870                 (me.fireEvent('beforeitem' + newType, me, record, item, index, e) === false) ||
96871                 (me['onItem' + map[newType]](record, item, index, e) === false)
96872             ) {
96873                 return false;
96874             }
96875
96876             me.fireEvent('item' + newType, me, record, item, index, e);
96877         }
96878         else {
96879             if (
96880                 (me.processContainerEvent(e) === false) ||
96881                 (me['onBeforeContainer' + map[type]](e) === false) ||
96882                 (me.fireEvent('beforecontainer' + type, me, e) === false) ||
96883                 (me['onContainer' + map[type]](e) === false)
96884             ) {
96885                 return false;
96886             }
96887
96888             me.fireEvent('container' + type, me, e);
96889         }
96890
96891         return true;
96892     },
96893
96894     isNewItemEvent: function (item, e) {
96895         var me = this,
96896             overItem = me.mouseOverItem,
96897             type = e.type;
96898
96899         switch (type) {
96900             case 'mouseover':
96901                 if (item === overItem) {
96902                     return false;
96903                 }
96904                 me.mouseOverItem = item;
96905                 return 'mouseenter';
96906
96907             case 'mouseout':
96908                 // If the currently mouseovered item contains the mouseover target, it's *NOT* a mouseleave
96909                 if (me.stillOverItem(e, overItem)) {
96910                     return false;
96911                 }
96912                 me.mouseOverItem = null;
96913                 return 'mouseleave';
96914         }
96915         return type;
96916     },
96917
96918     // private
96919     onItemMouseEnter: function(record, item, index, e) {
96920         if (this.trackOver) {
96921             this.highlightItem(item);
96922         }
96923     },
96924
96925     // private
96926     onItemMouseLeave : function(record, item, index, e) {
96927         if (this.trackOver) {
96928             this.clearHighlight();
96929         }
96930     },
96931
96932     // @private, template methods
96933     onItemMouseDown: Ext.emptyFn,
96934     onItemMouseUp: Ext.emptyFn,
96935     onItemClick: Ext.emptyFn,
96936     onItemDblClick: Ext.emptyFn,
96937     onItemContextMenu: Ext.emptyFn,
96938     onItemKeyDown: Ext.emptyFn,
96939     onBeforeItemMouseDown: Ext.emptyFn,
96940     onBeforeItemMouseUp: Ext.emptyFn,
96941     onBeforeItemMouseEnter: Ext.emptyFn,
96942     onBeforeItemMouseLeave: Ext.emptyFn,
96943     onBeforeItemClick: Ext.emptyFn,
96944     onBeforeItemDblClick: Ext.emptyFn,
96945     onBeforeItemContextMenu: Ext.emptyFn,
96946     onBeforeItemKeyDown: Ext.emptyFn,
96947
96948     // @private, template methods
96949     onContainerMouseDown: Ext.emptyFn,
96950     onContainerMouseUp: Ext.emptyFn,
96951     onContainerMouseOver: Ext.emptyFn,
96952     onContainerMouseOut: Ext.emptyFn,
96953     onContainerClick: Ext.emptyFn,
96954     onContainerDblClick: Ext.emptyFn,
96955     onContainerContextMenu: Ext.emptyFn,
96956     onContainerKeyDown: Ext.emptyFn,
96957     onBeforeContainerMouseDown: Ext.emptyFn,
96958     onBeforeContainerMouseUp: Ext.emptyFn,
96959     onBeforeContainerMouseOver: Ext.emptyFn,
96960     onBeforeContainerMouseOut: Ext.emptyFn,
96961     onBeforeContainerClick: Ext.emptyFn,
96962     onBeforeContainerDblClick: Ext.emptyFn,
96963     onBeforeContainerContextMenu: Ext.emptyFn,
96964     onBeforeContainerKeyDown: Ext.emptyFn,
96965
96966     /**
96967      * Highlight a given item in the DataView. This is called by the mouseover handler if {@link #overItemCls}
96968      * and {@link #trackOver} are configured, but can also be called manually by other code, for instance to
96969      * handle stepping through the list via keyboard navigation.
96970      * @param {HTMLElement} item The item to highlight
96971      */
96972     highlightItem: function(item) {
96973         var me = this;
96974         me.clearHighlight();
96975         me.highlightedItem = item;
96976         Ext.fly(item).addCls(me.overItemCls);
96977     },
96978
96979     /**
96980      * Un-highlight the currently highlighted item, if any.
96981      */
96982     clearHighlight: function() {
96983         var me = this,
96984             highlighted = me.highlightedItem;
96985
96986         if (highlighted) {
96987             Ext.fly(highlighted).removeCls(me.overItemCls);
96988             delete me.highlightedItem;
96989         }
96990     },
96991
96992     refresh: function() {
96993         this.clearHighlight();
96994         this.callParent(arguments);
96995     }
96996 });
96997 /**
96998  * Component layout for {@link Ext.view.BoundList}. Handles constraining the height to the configured maxHeight.
96999  * @class Ext.layout.component.BoundList
97000  * @extends Ext.layout.component.Component
97001  * @private
97002  */
97003 Ext.define('Ext.layout.component.BoundList', {
97004     extend: 'Ext.layout.component.Component',
97005     alias: 'layout.boundlist',
97006
97007     type: 'component',
97008
97009     beforeLayout: function() {
97010         return this.callParent(arguments) || this.owner.refreshed > 0;
97011     },
97012
97013     onLayout : function(width, height) {
97014         var me = this,
97015             owner = me.owner,
97016             floating = owner.floating,
97017             el = owner.el,
97018             xy = el.getXY(),
97019             isNumber = Ext.isNumber,
97020             minWidth, maxWidth, minHeight, maxHeight,
97021             naturalWidth, naturalHeight, constrainedWidth, constrainedHeight, undef;
97022
97023         if (floating) {
97024             // Position offscreen so the natural width is not affected by the viewport's right edge
97025             el.setXY([-9999,-9999]);
97026         }
97027
97028         // Calculate initial layout
97029         me.setTargetSize(width, height);
97030
97031         // Handle min/maxWidth for auto-width
97032         if (!isNumber(width)) {
97033             minWidth = owner.minWidth;
97034             maxWidth = owner.maxWidth;
97035             if (isNumber(minWidth) || isNumber(maxWidth)) {
97036                 naturalWidth = el.getWidth();
97037                 if (naturalWidth < minWidth) {
97038                     constrainedWidth = minWidth;
97039                 }
97040                 else if (naturalWidth > maxWidth) {
97041                     constrainedWidth = maxWidth;
97042                 }
97043                 if (constrainedWidth) {
97044                     me.setTargetSize(constrainedWidth);
97045                 }
97046             }
97047         }
97048         // Handle min/maxHeight for auto-height
97049         if (!isNumber(height)) {
97050             minHeight = owner.minHeight;
97051             maxHeight = owner.maxHeight;
97052             if (isNumber(minHeight) || isNumber(maxHeight)) {
97053                 naturalHeight = el.getHeight();
97054                 if (naturalHeight < minHeight) {
97055                     constrainedHeight = minHeight;
97056                 }
97057                 else if (naturalHeight > maxHeight) {
97058                     constrainedHeight = maxHeight;
97059                 }
97060                 if (constrainedHeight) {
97061                     me.setTargetSize(undef, constrainedHeight);
97062                 }
97063             }
97064         }
97065
97066         if (floating) {
97067             // Restore position
97068             el.setXY(xy);
97069         }
97070     },
97071
97072     afterLayout: function() {
97073         var me = this,
97074             toolbar = me.owner.pagingToolbar;
97075         me.callParent();
97076         if (toolbar) {
97077             toolbar.doComponentLayout();
97078         }
97079     },
97080
97081     setTargetSize : function(width, height) {
97082         var me = this,
97083             owner = me.owner,
97084             listHeight = null,
97085             toolbar;
97086
97087         // Size the listEl
97088         if (Ext.isNumber(height)) {
97089             listHeight = height - owner.el.getFrameWidth('tb');
97090             toolbar = owner.pagingToolbar;
97091             if (toolbar) {
97092                 listHeight -= toolbar.getHeight();
97093             }
97094         }
97095         me.setElementSize(owner.listEl, null, listHeight);
97096
97097         me.callParent(arguments);
97098     }
97099
97100 });
97101
97102 /**
97103  * @class Ext.toolbar.TextItem
97104  * @extends Ext.toolbar.Item
97105  *
97106  * A simple class that renders text directly into a toolbar.
97107  *
97108  * ## Example usage
97109  *
97110  * {@img Ext.toolbar.TextItem/Ext.toolbar.TextItem.png TextItem component}
97111  *
97112  *      Ext.create('Ext.panel.Panel', {
97113  *          title: 'Panel with TextItem',
97114  *          width: 300,
97115  *          height: 200,
97116  *          tbar: [
97117  *              {xtype: 'tbtext', text: 'Sample TextItem'}
97118  *          ],
97119  *          renderTo: Ext.getBody()
97120  *      });
97121  *
97122  * @constructor
97123  * Creates a new TextItem
97124  * @param {Object} text A text string, or a config object containing a <tt>text</tt> property
97125  * @xtype tbtext
97126  */
97127 Ext.define('Ext.toolbar.TextItem', {
97128     extend: 'Ext.toolbar.Item',
97129     requires: ['Ext.XTemplate'],
97130     alias: 'widget.tbtext',
97131     alternateClassName: 'Ext.Toolbar.TextItem',
97132     
97133     /**
97134      * @cfg {String} text The text to be used as innerHTML (html tags are accepted)
97135      */
97136     text: '',
97137     
97138     renderTpl: '{text}',
97139     //
97140     baseCls: Ext.baseCSSPrefix + 'toolbar-text',
97141     
97142     onRender : function() {
97143         Ext.apply(this.renderData, {
97144             text: this.text
97145         });
97146         this.callParent(arguments);
97147     },
97148
97149     /**
97150      * Updates this item's text, setting the text to be used as innerHTML.
97151      * @param {String} t The text to display (html accepted).
97152      */
97153     setText : function(t) {
97154         if (this.rendered) {
97155             this.el.update(t);
97156             this.ownerCt.doLayout(); // In case an empty text item (centered at zero height) receives new text.
97157         } else {
97158             this.text = t;
97159         }
97160     }
97161 });
97162 /**
97163  * @class Ext.form.field.Trigger
97164  * @extends Ext.form.field.Text
97165  * <p>Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
97166  * The trigger has no default action, so you must assign a function to implement the trigger click handler by
97167  * overriding {@link #onTriggerClick}. You can create a Trigger field directly, as it renders exactly like a combobox
97168  * for which you can provide a custom implementation. 
97169  * {@img Ext.form.field.Trigger/Ext.form.field.Trigger.png Ext.form.field.Trigger component}
97170  * For example:</p>
97171  * <pre><code>
97172 Ext.define('Ext.ux.CustomTrigger', {
97173     extend: 'Ext.form.field.Trigger',
97174     alias: 'widget.customtrigger',
97175     
97176     // override onTriggerClick
97177     onTriggerClick: function() {
97178         Ext.Msg.alert('Status', 'You clicked my trigger!');
97179     }
97180 });
97181
97182 Ext.create('Ext.form.FormPanel', {
97183     title: 'Form with TriggerField',
97184     bodyPadding: 5,
97185     width: 350,
97186     renderTo: Ext.getBody(),
97187     items:[{
97188         xtype: 'customtrigger',
97189         fieldLabel: 'Sample Trigger',
97190         emptyText: 'click the trigger',
97191     }]
97192 });
97193 </code></pre>
97194  *
97195  * <p>However, in general you will most likely want to use Trigger as the base class for a reusable component.
97196  * {@link Ext.form.field.Date} and {@link Ext.form.field.ComboBox} are perfect examples of this.</p>
97197  *
97198  * @constructor
97199  * Create a new Trigger field.
97200  * @param {Object} config Configuration options (valid {@Ext.form.field.Text} config options will also be applied
97201  * to the base Text field)
97202  * @xtype triggerfield
97203  */
97204 Ext.define('Ext.form.field.Trigger', {
97205     extend:'Ext.form.field.Text',
97206     alias: ['widget.triggerfield', 'widget.trigger'],
97207     requires: ['Ext.core.DomHelper', 'Ext.util.ClickRepeater', 'Ext.layout.component.field.Trigger'],
97208     alternateClassName: ['Ext.form.TriggerField', 'Ext.form.TwinTriggerField', 'Ext.form.Trigger'],
97209
97210     fieldSubTpl: [
97211         '<input id="{id}" type="{type}" ',
97212             '<tpl if="name">name="{name}" </tpl>',
97213             '<tpl if="size">size="{size}" </tpl>',
97214             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
97215             'class="{fieldCls} {typeCls}" autocomplete="off" />',
97216         '<div class="{triggerWrapCls}" role="presentation">',
97217             '{triggerEl}',
97218             '<div class="{clearCls}" role="presentation"></div>',
97219         '</div>',
97220         {
97221             compiled: true,
97222             disableFormats: true
97223         }
97224     ],
97225
97226     /**
97227      * @cfg {String} triggerCls
97228      * An additional CSS class used to style the trigger button.  The trigger will always get the
97229      * {@link #triggerBaseCls} by default and <tt>triggerCls</tt> will be <b>appended</b> if specified.
97230      * Defaults to undefined.
97231      */
97232
97233     /**
97234      * @cfg {String} triggerBaseCls
97235      * The base CSS class that is always added to the trigger button. The {@link #triggerCls} will be
97236      * appended in addition to this class.
97237      */
97238     triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger',
97239
97240     /**
97241      * @cfg {String} triggerWrapCls
97242      * The CSS class that is added to the div wrapping the trigger button(s).
97243      */
97244     triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap',
97245
97246     /**
97247      * @cfg {Boolean} hideTrigger <tt>true</tt> to hide the trigger element and display only the base
97248      * text field (defaults to <tt>false</tt>)
97249      */
97250     hideTrigger: false,
97251
97252     /**
97253      * @cfg {Boolean} editable <tt>false</tt> to prevent the user from typing text directly into the field;
97254      * the field can only have its value set via an action invoked by the trigger. (defaults to <tt>true</tt>).
97255      */
97256     editable: true,
97257
97258     /**
97259      * @cfg {Boolean} readOnly <tt>true</tt> to prevent the user from changing the field, and
97260      * hides the trigger.  Supercedes the editable and hideTrigger options if the value is true.
97261      * (defaults to <tt>false</tt>)
97262      */
97263     readOnly: false,
97264
97265     /**
97266      * @cfg {Boolean} selectOnFocus <tt>true</tt> to select any existing text in the field immediately on focus.
97267      * Only applies when <tt>{@link #editable editable} = true</tt> (defaults to <tt>false</tt>).
97268      */
97269
97270     /**
97271      * @cfg {Boolean} repeatTriggerClick <tt>true</tt> to attach a {@link Ext.util.ClickRepeater click repeater}
97272      * to the trigger. Defaults to <tt>false</tt>.
97273      */
97274     repeatTriggerClick: false,
97275
97276
97277     /**
97278      * @hide
97279      * @method autoSize
97280      */
97281     autoSize: Ext.emptyFn,
97282     // private
97283     monitorTab: true,
97284     // private
97285     mimicing: false,
97286     // private
97287     triggerIndexRe: /trigger-index-(\d+)/,
97288
97289     componentLayout: 'triggerfield',
97290
97291     initComponent: function() {
97292         this.wrapFocusCls = this.triggerWrapCls + '-focus';
97293         this.callParent(arguments);
97294     },
97295
97296     // private
97297     onRender: function(ct, position) {
97298         var me = this,
97299             triggerCls,
97300             triggerBaseCls = me.triggerBaseCls,
97301             triggerWrapCls = me.triggerWrapCls,
97302             triggerConfigs = [],
97303             i;
97304
97305         // triggerCls is a synonym for trigger1Cls, so copy it.
97306         // TODO this trigger<n>Cls API design doesn't feel clean, especially where it butts up against the
97307         // single triggerCls config. Should rethink this, perhaps something more structured like a list of
97308         // trigger config objects that hold cls, handler, etc.
97309         if (!me.trigger1Cls) {
97310             me.trigger1Cls = me.triggerCls;
97311         }
97312
97313         // Create as many trigger elements as we have trigger<n>Cls configs, but always at least one
97314         for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i < 1; i++) {
97315             triggerConfigs.push({
97316                 cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '),
97317                 role: 'button'
97318             });
97319         }
97320         triggerConfigs[i - 1].cls += ' ' + triggerBaseCls + '-last';
97321
97322         Ext.applyIf(me.renderSelectors, {
97323             /**
97324              * @property triggerWrap
97325              * @type Ext.core.Element
97326              * A reference to the div element wrapping the trigger button(s). Only set after the field has been rendered.
97327              */
97328             triggerWrap: '.' + triggerWrapCls
97329         });
97330         Ext.applyIf(me.subTplData, {
97331             triggerWrapCls: triggerWrapCls,
97332             triggerEl: Ext.core.DomHelper.markup(triggerConfigs),
97333             clearCls: me.clearCls
97334         });
97335
97336         me.callParent(arguments);
97337
97338         /**
97339          * @property triggerEl
97340          * @type Ext.CompositeElement
97341          * A composite of all the trigger button elements. Only set after the field has been rendered.
97342          */
97343         me.triggerEl = Ext.select('.' + triggerBaseCls, true, me.triggerWrap.dom);
97344
97345         me.doc = Ext.isIE ? Ext.getBody() : Ext.getDoc();
97346         me.initTrigger();
97347     },
97348
97349     onEnable: function() {
97350         this.callParent();
97351         this.triggerWrap.unmask();
97352     },
97353     
97354     onDisable: function() {
97355         this.callParent();
97356         this.triggerWrap.mask();
97357     },
97358     
97359     afterRender: function() {
97360         this.callParent();
97361         this.updateEditState();
97362     },
97363
97364     updateEditState: function() {
97365         var me = this,
97366             inputEl = me.inputEl,
97367             triggerWrap = me.triggerWrap,
97368             noeditCls = Ext.baseCSSPrefix + 'trigger-noedit',
97369             displayed,
97370             readOnly;
97371
97372         if (me.rendered) {
97373             if (me.readOnly) {
97374                 inputEl.addCls(noeditCls);
97375                 readOnly = true;
97376                 displayed = false;
97377             } else {
97378                 if (me.editable) {
97379                     inputEl.removeCls(noeditCls);
97380                     readOnly = false;
97381                 } else {
97382                     inputEl.addCls(noeditCls);
97383                     readOnly = true;
97384                 }
97385                 displayed = !me.hideTrigger;
97386             }
97387
97388             triggerWrap.setDisplayed(displayed);
97389             inputEl.dom.readOnly = readOnly;
97390             me.doComponentLayout();
97391         }
97392     },
97393
97394     /**
97395      * Get the total width of the trigger button area. Only useful after the field has been rendered.
97396      * @return {Number} The trigger width
97397      */
97398     getTriggerWidth: function() {
97399         var me = this,
97400             triggerWrap = me.triggerWrap,
97401             totalTriggerWidth = 0;
97402         if (triggerWrap && !me.hideTrigger && !me.readOnly) {
97403             me.triggerEl.each(function(trigger) {
97404                 totalTriggerWidth += trigger.getWidth();
97405             });
97406             totalTriggerWidth += me.triggerWrap.getFrameWidth('lr');
97407         }
97408         return totalTriggerWidth;
97409     },
97410
97411     setHideTrigger: function(hideTrigger) {
97412         if (hideTrigger != this.hideTrigger) {
97413             this.hideTrigger = hideTrigger;
97414             this.updateEditState();
97415         }
97416     },
97417
97418     /**
97419      * @param {Boolean} editable True to allow the user to directly edit the field text
97420      * Allow or prevent the user from directly editing the field text.  If false is passed,
97421      * the user will only be able to modify the field using the trigger.  Will also add
97422      * a click event to the text field which will call the trigger. This method
97423      * is the runtime equivalent of setting the 'editable' config option at config time.
97424      */
97425     setEditable: function(editable) {
97426         if (editable != this.editable) {
97427             this.editable = editable;
97428             this.updateEditState();
97429         }
97430     },
97431
97432     /**
97433      * @param {Boolean} readOnly True to prevent the user changing the field and explicitly
97434      * hide the trigger.
97435      * Setting this to true will superceed settings editable and hideTrigger.
97436      * Setting this to false will defer back to editable and hideTrigger. This method
97437      * is the runtime equivalent of setting the 'readOnly' config option at config time.
97438      */
97439     setReadOnly: function(readOnly) {
97440         if (readOnly != this.readOnly) {
97441             this.readOnly = readOnly;
97442             this.updateEditState();
97443         }
97444     },
97445
97446     // private
97447     initTrigger: function() {
97448         var me = this,
97449             triggerWrap = me.triggerWrap,
97450             triggerEl = me.triggerEl;
97451
97452         if (me.repeatTriggerClick) {
97453             me.triggerRepeater = Ext.create('Ext.util.ClickRepeater', triggerWrap, {
97454                 preventDefault: true,
97455                 handler: function(cr, e) {
97456                     me.onTriggerWrapClick(e);
97457                 }
97458             });
97459         } else {
97460             me.mon(me.triggerWrap, 'click', me.onTriggerWrapClick, me);
97461         }
97462
97463         triggerEl.addClsOnOver(me.triggerBaseCls + '-over');
97464         triggerEl.each(function(el, c, i) {
97465             el.addClsOnOver(me['trigger' + (i + 1) + 'Cls'] + '-over');
97466         });
97467         triggerEl.addClsOnClick(me.triggerBaseCls + '-click');
97468         triggerEl.each(function(el, c, i) {
97469             el.addClsOnClick(me['trigger' + (i + 1) + 'Cls'] + '-click');
97470         });
97471     },
97472
97473     // private
97474     onDestroy: function() {
97475         var me = this;
97476         Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl');
97477         delete me.doc;
97478         me.callParent();
97479     },
97480
97481     // private
97482     onFocus: function() {
97483         var me = this;
97484         this.callParent();
97485         if (!me.mimicing) {
97486             me.bodyEl.addCls(me.wrapFocusCls);
97487             me.mimicing = true;
97488             me.mon(me.doc, 'mousedown', me.mimicBlur, me, {
97489                 delay: 10
97490             });
97491             if (me.monitorTab) {
97492                 me.on('specialkey', me.checkTab, me);
97493             }
97494         }
97495     },
97496
97497     // private
97498     checkTab: function(me, e) {
97499         if (!this.ignoreMonitorTab && e.getKey() == e.TAB) {
97500             this.triggerBlur();
97501         }
97502     },
97503
97504     // private
97505     onBlur: Ext.emptyFn,
97506
97507     // private
97508     mimicBlur: function(e) {
97509         if (!this.isDestroyed && !this.bodyEl.contains(e.target) && this.validateBlur(e)) {
97510             this.triggerBlur();
97511         }
97512     },
97513
97514     // private
97515     triggerBlur: function() {
97516         var me = this;
97517         me.mimicing = false;
97518         me.mun(me.doc, 'mousedown', me.mimicBlur, me);
97519         if (me.monitorTab && me.inputEl) {
97520             me.un('specialkey', me.checkTab, me);
97521         }
97522         Ext.form.field.Trigger.superclass.onBlur.call(me);
97523         if (me.bodyEl) {
97524             me.bodyEl.removeCls(me.wrapFocusCls);
97525         }
97526     },
97527
97528     beforeBlur: Ext.emptyFn,
97529
97530     // private
97531     // This should be overridden by any subclass that needs to check whether or not the field can be blurred.
97532     validateBlur: function(e) {
97533         return true;
97534     },
97535
97536     // private
97537     // process clicks upon triggers.
97538     // determine which trigger index, and dispatch to the appropriate click handler
97539     onTriggerWrapClick: function(e) {
97540         var me = this,
97541             t = e && e.getTarget('.' + Ext.baseCSSPrefix + 'form-trigger', null),
97542             match = t && t.className.match(me.triggerIndexRe),
97543             idx,
97544             triggerClickMethod;
97545
97546         if (match && !me.readOnly) {
97547             idx = parseInt(match[1], 10);
97548             triggerClickMethod = me['onTrigger' + (idx + 1) + 'Click'] || me.onTriggerClick;
97549             if (triggerClickMethod) {
97550                 triggerClickMethod.call(me, e);
97551             }
97552         }
97553     },
97554
97555     /**
97556      * The function that should handle the trigger's click event.  This method does nothing by default
97557      * until overridden by an implementing function.  See Ext.form.field.ComboBox and Ext.form.field.Date for
97558      * sample implementations.
97559      * @method
97560      * @param {Ext.EventObject} e
97561      */
97562     onTriggerClick: Ext.emptyFn
97563
97564     /**
97565      * @cfg {Boolean} grow @hide
97566      */
97567     /**
97568      * @cfg {Number} growMin @hide
97569      */
97570     /**
97571      * @cfg {Number} growMax @hide
97572      */
97573 });
97574
97575 /**
97576  * @class Ext.form.field.Picker
97577  * @extends Ext.form.field.Trigger
97578  * <p>An abstract class for fields that have a single trigger which opens a "picker" popup below
97579  * the field, e.g. a combobox menu list or a date picker. It provides a base implementation for
97580  * toggling the picker's visibility when the trigger is clicked, as well as keyboard navigation
97581  * and some basic events. Sizing and alignment of the picker can be controlled via the {@link #matchFieldWidth}
97582  * and {@link #pickerAlign}/{@link #pickerOffset} config properties respectively.</p>
97583  * <p>You would not normally use this class directly, but instead use it as the parent class for
97584  * a specific picker field implementation. Subclasses must implement the {@link #createPicker} method
97585  * to create a picker component appropriate for the field.</p>
97586  *
97587  * @xtype pickerfield
97588  * @constructor
97589  * Create a new picker field
97590  * @param {Object} config
97591  */
97592 Ext.define('Ext.form.field.Picker', {
97593     extend: 'Ext.form.field.Trigger',
97594     alias: 'widget.pickerfield',
97595     alternateClassName: 'Ext.form.Picker',
97596     requires: ['Ext.util.KeyNav'],
97597
97598     /**
97599      * @cfg {Boolean} matchFieldWidth
97600      * Whether the picker dropdown's width should be explicitly set to match the width of the field.
97601      * Defaults to <tt>true</tt>.
97602      */
97603     matchFieldWidth: true,
97604
97605     /**
97606      * @cfg {String} pickerAlign
97607      * The {@link Ext.core.Element#alignTo alignment position} with which to align the picker. Defaults
97608      * to <tt>"tl-bl?"</tt>
97609      */
97610     pickerAlign: 'tl-bl?',
97611
97612     /**
97613      * @cfg {Array} pickerOffset
97614      * An offset [x,y] to use in addition to the {@link #pickerAlign} when positioning the picker.
97615      * Defaults to undefined.
97616      */
97617
97618     /**
97619      * @cfg {String} openCls
97620      * A class to be added to the field's {@link #bodyEl} element when the picker is opened. Defaults
97621      * to 'x-pickerfield-open'.
97622      */
97623     openCls: Ext.baseCSSPrefix + 'pickerfield-open',
97624
97625     /**
97626      * @property isExpanded
97627      * @type Boolean
97628      * True if the picker is currently expanded, false if not.
97629      */
97630
97631     /**
97632      * @cfg {Boolean} editable <tt>false</tt> to prevent the user from typing text directly into the field;
97633      * the field can only have its value set via selecting a value from the picker. In this state, the picker
97634      * can also be opened by clicking directly on the input field itself.
97635      * (defaults to <tt>true</tt>).
97636      */
97637     editable: true,
97638
97639
97640     initComponent: function() {
97641         this.callParent();
97642
97643         // Custom events
97644         this.addEvents(
97645             /**
97646              * @event expand
97647              * Fires when the field's picker is expanded.
97648              * @param {Ext.form.field.Picker} field This field instance
97649              */
97650             'expand',
97651             /**
97652              * @event collapse
97653              * Fires when the field's picker is collapsed.
97654              * @param {Ext.form.field.Picker} field This field instance
97655              */
97656             'collapse',
97657             /**
97658              * @event select
97659              * Fires when a value is selected via the picker.
97660              * @param {Ext.form.field.Picker} field This field instance
97661              * @param {Mixed} value The value that was selected. The exact type of this value is dependent on
97662              * the individual field and picker implementations.
97663              */
97664             'select'
97665         );
97666     },
97667
97668
97669     initEvents: function() {
97670         var me = this;
97671         me.callParent();
97672
97673         // Add handlers for keys to expand/collapse the picker
97674         me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
97675             down: function() {
97676                 if (!me.isExpanded) {
97677                     // Don't call expand() directly as there may be additional processing involved before
97678                     // expanding, e.g. in the case of a ComboBox query.
97679                     me.onTriggerClick();
97680                 }
97681             },
97682             esc: me.collapse,
97683             scope: me,
97684             forceKeyDown: true
97685         });
97686
97687         // Non-editable allows opening the picker by clicking the field
97688         if (!me.editable) {
97689             me.mon(me.inputEl, 'click', me.onTriggerClick, me);
97690         }
97691
97692         // Disable native browser autocomplete
97693         if (Ext.isGecko) {
97694             me.inputEl.dom.setAttribute('autocomplete', 'off');
97695         }
97696     },
97697
97698
97699     /**
97700      * Expand this field's picker dropdown.
97701      */
97702     expand: function() {
97703         var me = this,
97704             bodyEl, picker, collapseIf;
97705
97706         if (me.rendered && !me.isExpanded && !me.isDestroyed) {
97707             bodyEl = me.bodyEl;
97708             picker = me.getPicker();
97709             collapseIf = me.collapseIf;
97710
97711             // show the picker and set isExpanded flag
97712             picker.show();
97713             me.isExpanded = true;
97714             me.alignPicker();
97715             bodyEl.addCls(me.openCls);
97716
97717             // monitor clicking and mousewheel
97718             me.mon(Ext.getDoc(), {
97719                 mousewheel: collapseIf,
97720                 mousedown: collapseIf,
97721                 scope: me
97722             });
97723             Ext.EventManager.onWindowResize(me.alignPicker, me);
97724             me.fireEvent('expand', me);
97725             me.onExpand();
97726         }
97727     },
97728
97729     onExpand: Ext.emptyFn,
97730
97731     /**
97732      * @protected
97733      * Aligns the picker to the
97734      */
97735     alignPicker: function() {
97736         var me = this,
97737             picker, isAbove,
97738             aboveSfx = '-above';
97739
97740         if (this.isExpanded) {
97741             picker = me.getPicker();
97742             if (me.matchFieldWidth) {
97743                 // Auto the height (it will be constrained by min and max width) unless there are no records to display.
97744                 picker.setSize(me.bodyEl.getWidth(), picker.store && picker.store.getCount() ? null : 0);
97745             }
97746             if (picker.isFloating()) {
97747                 picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);
97748
97749                 // add the {openCls}-above class if the picker was aligned above
97750                 // the field due to hitting the bottom of the viewport
97751                 isAbove = picker.el.getY() < me.inputEl.getY();
97752                 me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
97753                 picker.el[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
97754             }
97755         }
97756     },
97757
97758     /**
97759      * Collapse this field's picker dropdown.
97760      */
97761     collapse: function() {
97762         if (this.isExpanded && !this.isDestroyed) {
97763             var me = this,
97764                 openCls = me.openCls,
97765                 picker = me.picker,
97766                 doc = Ext.getDoc(),
97767                 collapseIf = me.collapseIf,
97768                 aboveSfx = '-above';
97769
97770             // hide the picker and set isExpanded flag
97771             picker.hide();
97772             me.isExpanded = false;
97773
97774             // remove the openCls
97775             me.bodyEl.removeCls([openCls, openCls + aboveSfx]);
97776             picker.el.removeCls(picker.baseCls + aboveSfx);
97777
97778             // remove event listeners
97779             doc.un('mousewheel', collapseIf, me);
97780             doc.un('mousedown', collapseIf, me);
97781             Ext.EventManager.removeResizeListener(me.alignPicker, me);
97782             me.fireEvent('collapse', me);
97783             me.onCollapse();
97784         }
97785     },
97786
97787     onCollapse: Ext.emptyFn,
97788
97789
97790     /**
97791      * @private
97792      * Runs on mousewheel and mousedown of doc to check to see if we should collapse the picker
97793      */
97794     collapseIf: function(e) {
97795         var me = this;
97796         if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !e.within(me.picker.el, false, true)) {
97797             me.collapse();
97798         }
97799     },
97800
97801     /**
97802      * Return a reference to the picker component for this field, creating it if necessary by
97803      * calling {@link #createPicker}.
97804      * @return {Ext.Component} The picker component
97805      */
97806     getPicker: function() {
97807         var me = this;
97808         return me.picker || (me.picker = me.createPicker());
97809     },
97810
97811     /**
97812      * Create and return the component to be used as this field's picker. Must be implemented
97813      * by subclasses of Picker.
97814      * @return {Ext.Component} The picker component
97815      */
97816     createPicker: Ext.emptyFn,
97817
97818     /**
97819      * Handles the trigger click; by default toggles between expanding and collapsing the
97820      * picker component.
97821      */
97822     onTriggerClick: function() {
97823         var me = this;
97824         if (!me.readOnly && !me.disabled) {
97825             if (me.isExpanded) {
97826                 me.collapse();
97827             } else {
97828                 me.expand();
97829             }
97830             me.inputEl.focus();
97831         }
97832     },
97833
97834     mimicBlur: function(e) {
97835         var me = this,
97836             picker = me.picker;
97837         // ignore mousedown events within the picker element
97838         if (!picker || !e.within(picker.el, false, true)) {
97839             me.callParent(arguments);
97840         }
97841     },
97842
97843     onDestroy : function(){
97844         var me = this;
97845         Ext.EventManager.removeResizeListener(me.alignPicker, me);
97846         Ext.destroy(me.picker, me.keyNav);
97847         me.callParent();
97848     }
97849
97850 });
97851
97852
97853 /**
97854  * @class Ext.form.field.Spinner
97855  * @extends Ext.form.field.Trigger
97856  * <p>A field with a pair of up/down spinner buttons. This class is not normally instantiated directly,
97857  * instead it is subclassed and the {@link #onSpinUp} and {@link #onSpinDown} methods are implemented
97858  * to handle when the buttons are clicked. A good example of this is the {@link Ext.form.field.Number} field
97859  * which uses the spinner to increment and decrement the field's value by its {@link Ext.form.field.Number#step step}
97860  * config value.</p>
97861  * {@img Ext.form.field.Spinner/Ext.form.field.Spinner.png Ext.form.field.Spinner field}
97862  * For example:
97863      Ext.define('Ext.ux.CustomSpinner', {
97864         extend: 'Ext.form.field.Spinner',
97865         alias: 'widget.customspinner',
97866         
97867         // override onSpinUp (using step isn't neccessary)
97868         onSpinUp: function() {
97869             var me = this;
97870             if (!me.readOnly) {
97871                 var val = me.step; // set the default value to the step value
97872                 if(me.getValue() !== '') {
97873                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
97874                 }                          
97875                 me.setValue((val + me.step) + ' Pack');
97876             }
97877         },
97878         
97879         // override onSpinDown
97880         onSpinDown: function() {
97881             var me = this;
97882             if (!me.readOnly) {
97883                 if(me.getValue() !== '') {
97884                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
97885                 }            
97886                 me.setValue((val - me.step) + ' Pack');
97887             }
97888         }
97889     });
97890     
97891     Ext.create('Ext.form.FormPanel', {
97892         title: 'Form with SpinnerField',
97893         bodyPadding: 5,
97894         width: 350,
97895         renderTo: Ext.getBody(),
97896         items:[{
97897             xtype: 'customspinner',
97898             fieldLabel: 'How Much Beer?',
97899             step: 6
97900         }]
97901     });
97902  * <p>By default, pressing the up and down arrow keys will also trigger the onSpinUp and onSpinDown methods;
97903  * to prevent this, set <tt>{@link #keyNavEnabled} = false</tt>.</p>
97904  *
97905  * @constructor
97906  * Creates a new Spinner field
97907  * @param {Object} config Configuration options
97908  * @xtype spinnerfield
97909  */
97910 Ext.define('Ext.form.field.Spinner', {
97911     extend: 'Ext.form.field.Trigger',
97912     alias: 'widget.spinnerfield',
97913     alternateClassName: 'Ext.form.Spinner',
97914     requires: ['Ext.util.KeyNav'],
97915
97916     trigger1Cls: Ext.baseCSSPrefix + 'form-spinner-up',
97917     trigger2Cls: Ext.baseCSSPrefix + 'form-spinner-down',
97918
97919     /**
97920      * @cfg {Boolean} spinUpEnabled
97921      * Specifies whether the up spinner button is enabled. Defaults to <tt>true</tt>. To change this
97922      * after the component is created, use the {@link #setSpinUpEnabled} method.
97923      */
97924     spinUpEnabled: true,
97925
97926     /**
97927      * @cfg {Boolean} spinDownEnabled
97928      * Specifies whether the down spinner button is enabled. Defaults to <tt>true</tt>. To change this
97929      * after the component is created, use the {@link #setSpinDownEnabled} method.
97930      */
97931     spinDownEnabled: true,
97932
97933     /**
97934      * @cfg {Boolean} keyNavEnabled
97935      * Specifies whether the up and down arrow keys should trigger spinning up and down.
97936      * Defaults to <tt>true</tt>.
97937      */
97938     keyNavEnabled: true,
97939
97940     /**
97941      * @cfg {Boolean} mouseWheelEnabled
97942      * Specifies whether the mouse wheel should trigger spinning up and down while the field has
97943      * focus. Defaults to <tt>true</tt>.
97944      */
97945     mouseWheelEnabled: true,
97946
97947     /**
97948      * @cfg {Boolean} repeatTriggerClick Whether a {@link Ext.util.ClickRepeater click repeater} should be
97949      * attached to the spinner buttons. Defaults to <tt>true</tt>.
97950      */
97951     repeatTriggerClick: true,
97952
97953     /**
97954      * This method is called when the spinner up button is clicked, or when the up arrow key is pressed
97955      * if {@link #keyNavEnabled} is <tt>true</tt>. Must be implemented by subclasses.
97956      */
97957     onSpinUp: Ext.emptyFn,
97958
97959     /**
97960      * This method is called when the spinner down button is clicked, or when the down arrow key is pressed
97961      * if {@link #keyNavEnabled} is <tt>true</tt>. Must be implemented by subclasses.
97962      */
97963     onSpinDown: Ext.emptyFn,
97964
97965     initComponent: function() {
97966         this.callParent();
97967
97968         this.addEvents(
97969             /**
97970              * @event spin
97971              * Fires when the spinner is made to spin up or down.
97972              * @param {Ext.form.field.Spinner} this
97973              * @param {String} direction Either 'up' if spinning up, or 'down' if spinning down.
97974              */
97975             'spin',
97976
97977             /**
97978              * @event spinup
97979              * Fires when the spinner is made to spin up.
97980              * @param {Ext.form.field.Spinner} this
97981              */
97982             'spinup',
97983
97984             /**
97985              * @event spindown
97986              * Fires when the spinner is made to spin down.
97987              * @param {Ext.form.field.Spinner} this
97988              */
97989             'spindown'
97990         );
97991     },
97992
97993     /**
97994      * @private override
97995      */
97996     onRender: function() {
97997         var me = this,
97998             triggers;
97999
98000         me.callParent(arguments);
98001         triggers = me.triggerEl;
98002
98003         /**
98004          * @property spinUpEl
98005          * @type Ext.core.Element
98006          * The spinner up button element
98007          */
98008         me.spinUpEl = triggers.item(0);
98009         /**
98010          * @property spinDownEl
98011          * @type Ext.core.Element
98012          * The spinner down button element
98013          */
98014         me.spinDownEl = triggers.item(1);
98015
98016         // Set initial enabled/disabled states
98017         me.setSpinUpEnabled(me.spinUpEnabled);
98018         me.setSpinDownEnabled(me.spinDownEnabled);
98019
98020         // Init up/down arrow keys
98021         if (me.keyNavEnabled) {
98022             me.spinnerKeyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
98023                 scope: me,
98024                 up: me.spinUp,
98025                 down: me.spinDown
98026             });
98027         }
98028
98029         // Init mouse wheel
98030         if (me.mouseWheelEnabled) {
98031             me.mon(me.bodyEl, 'mousewheel', me.onMouseWheel, me);
98032         }
98033     },
98034
98035     /**
98036      * @private override
98037      * Since the triggers are stacked, only measure the width of one of them.
98038      */
98039     getTriggerWidth: function() {
98040         return this.hideTrigger || this.readOnly ? 0 : this.spinUpEl.getWidth() + this.triggerWrap.getFrameWidth('lr');
98041     },
98042
98043     /**
98044      * @private Handles the spinner up button clicks.
98045      */
98046     onTrigger1Click: function() {
98047         this.spinUp();
98048     },
98049
98050     /**
98051      * @private Handles the spinner down button clicks.
98052      */
98053     onTrigger2Click: function() {
98054         this.spinDown();
98055     },
98056
98057     /**
98058      * Triggers the spinner to step up; fires the {@link #spin} and {@link #spinup} events and calls the
98059      * {@link #onSpinUp} method. Does nothing if the field is {@link #disabled} or if {@link #spinUpEnabled}
98060      * is false.
98061      */
98062     spinUp: function() {
98063         var me = this;
98064         if (me.spinUpEnabled && !me.disabled) {
98065             me.fireEvent('spin', me, 'up');
98066             me.fireEvent('spinup', me);
98067             me.onSpinUp();
98068         }
98069     },
98070
98071     /**
98072      * Triggers the spinner to step down; fires the {@link #spin} and {@link #spindown} events and calls the
98073      * {@link #onSpinDown} method. Does nothing if the field is {@link #disabled} or if {@link #spinDownEnabled}
98074      * is false.
98075      */
98076     spinDown: function() {
98077         var me = this;
98078         if (me.spinDownEnabled && !me.disabled) {
98079             me.fireEvent('spin', me, 'down');
98080             me.fireEvent('spindown', me);
98081             me.onSpinDown();
98082         }
98083     },
98084
98085     /**
98086      * Sets whether the spinner up button is enabled.
98087      * @param {Boolean} enabled true to enable the button, false to disable it.
98088      */
98089     setSpinUpEnabled: function(enabled) {
98090         var me = this,
98091             wasEnabled = me.spinUpEnabled;
98092         me.spinUpEnabled = enabled;
98093         if (wasEnabled !== enabled && me.rendered) {
98094             me.spinUpEl[enabled ? 'removeCls' : 'addCls'](me.trigger1Cls + '-disabled');
98095         }
98096     },
98097
98098     /**
98099      * Sets whether the spinner down button is enabled.
98100      * @param {Boolean} enabled true to enable the button, false to disable it.
98101      */
98102     setSpinDownEnabled: function(enabled) {
98103         var me = this,
98104             wasEnabled = me.spinDownEnabled;
98105         me.spinDownEnabled = enabled;
98106         if (wasEnabled !== enabled && me.rendered) {
98107             me.spinDownEl[enabled ? 'removeCls' : 'addCls'](me.trigger2Cls + '-disabled');
98108         }
98109     },
98110
98111     /**
98112      * @private
98113      * Handles mousewheel events on the field
98114      */
98115     onMouseWheel: function(e) {
98116         var me = this,
98117             delta;
98118         if (me.hasFocus) {
98119             delta = e.getWheelDelta();
98120             if (delta > 0) {
98121                 me.spinUp();
98122             }
98123             else if (delta < 0) {
98124                 me.spinDown();
98125             }
98126             e.stopEvent();
98127         }
98128     },
98129
98130     onDestroy: function() {
98131         Ext.destroyMembers(this, 'spinnerKeyNav', 'spinUpEl', 'spinDownEl');
98132         this.callParent();
98133     }
98134
98135 });
98136 /**
98137  * @class Ext.form.field.Number
98138  * @extends Ext.form.field.Spinner
98139
98140 A numeric text field that provides automatic keystroke filtering to disallow non-numeric characters,
98141 and numeric validation to limit the value to a range of valid numbers. The range of acceptable number
98142 values can be controlled by setting the {@link #minValue} and {@link #maxValue} configs, and fractional
98143 decimals can be disallowed by setting {@link #allowDecimals} to `false`.
98144
98145 By default, the number field is also rendered with a set of up/down spinner buttons and has
98146 up/down arrow key and mouse wheel event listeners attached for incrementing/decrementing the value by the
98147 {@link #step} value. To hide the spinner buttons set `{@link #hideTrigger hideTrigger}:true`; to disable the arrow key
98148 and mouse wheel handlers set `{@link #keyNavEnabled keyNavEnabled}:false` and
98149 `{@link #mouseWheelEnabled mouseWheelEnabled}:false`. See the example below.
98150
98151 #Example usage:#
98152 {@img Ext.form.Number/Ext.form.Number1.png Ext.form.Number component}
98153     Ext.create('Ext.form.Panel', {
98154         title: 'On The Wall',
98155         width: 300,
98156         bodyPadding: 10,
98157         renderTo: Ext.getBody(),
98158         items: [{
98159             xtype: 'numberfield',
98160             anchor: '100%',
98161             name: 'bottles',
98162             fieldLabel: 'Bottles of Beer',
98163             value: 99,
98164             maxValue: 99,
98165             minValue: 0
98166         }],
98167         buttons: [{
98168             text: 'Take one down, pass it around',
98169             handler: function() {
98170                 this.up('form').down('[name=bottles]').spinDown();
98171             }
98172         }]
98173     });
98174
98175 #Removing UI Enhancements#
98176 {@img Ext.form.Number/Ext.form.Number2.png Ext.form.Number component}
98177     Ext.create('Ext.form.Panel', {
98178         title: 'Personal Info',
98179         width: 300,
98180         bodyPadding: 10,
98181         renderTo: Ext.getBody(),        
98182         items: [{
98183             xtype: 'numberfield',
98184             anchor: '100%',
98185             name: 'age',
98186             fieldLabel: 'Age',
98187             minValue: 0, //prevents negative numbers
98188     
98189             // Remove spinner buttons, and arrow key and mouse wheel listeners
98190             hideTrigger: true,
98191             keyNavEnabled: false,
98192             mouseWheelEnabled: false
98193         }]
98194     });
98195
98196 #Using Step#
98197     Ext.create('Ext.form.Panel', {
98198         renderTo: Ext.getBody(),
98199         title: 'Step',
98200         width: 300,
98201         bodyPadding: 10,
98202         items: [{
98203             xtype: 'numberfield',
98204             anchor: '100%',
98205             name: 'evens',
98206             fieldLabel: 'Even Numbers',
98207
98208             // Set step so it skips every other number
98209             step: 2,
98210             value: 0,
98211
98212             // Add change handler to force user-entered numbers to evens
98213             listeners: {
98214                 change: function(field, value) {
98215                     value = parseInt(value, 10);
98216                     field.setValue(value + value % 2);
98217                 }
98218             }
98219         }]
98220     });
98221
98222
98223  * @constructor
98224  * Creates a new Number field
98225  * @param {Object} config Configuration options
98226  *
98227  * @xtype numberfield
98228  * @markdown
98229  * @docauthor Jason Johnston <jason@sencha.com>
98230  */
98231 Ext.define('Ext.form.field.Number', {
98232     extend:'Ext.form.field.Spinner',
98233     alias: 'widget.numberfield',
98234     alternateClassName: ['Ext.form.NumberField', 'Ext.form.Number'],
98235
98236     /**
98237      * @cfg {RegExp} stripCharsRe @hide
98238      */
98239     /**
98240      * @cfg {RegExp} maskRe @hide
98241      */
98242
98243     /**
98244      * @cfg {Boolean} allowDecimals False to disallow decimal values (defaults to true)
98245      */
98246     allowDecimals : true,
98247
98248     /**
98249      * @cfg {String} decimalSeparator Character(s) to allow as the decimal separator (defaults to '.')
98250      */
98251     decimalSeparator : '.',
98252
98253     /**
98254      * @cfg {Number} decimalPrecision The maximum precision to display after the decimal separator (defaults to 2)
98255      */
98256     decimalPrecision : 2,
98257
98258     /**
98259      * @cfg {Number} minValue The minimum allowed value (defaults to Number.NEGATIVE_INFINITY). Will be used by
98260      * the field's validation logic, and for
98261      * {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the down spinner button}.
98262      */
98263     minValue: Number.NEGATIVE_INFINITY,
98264
98265     /**
98266      * @cfg {Number} maxValue The maximum allowed value (defaults to Number.MAX_VALUE). Will be used by
98267      * the field's validation logic, and for
98268      * {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the up spinner button}.
98269      */
98270     maxValue: Number.MAX_VALUE,
98271
98272     /**
98273      * @cfg {Number} step Specifies a numeric interval by which the field's value will be incremented or
98274      * decremented when the user invokes the spinner. Defaults to <tt>1</tt>.
98275      */
98276     step: 1,
98277
98278     /**
98279      * @cfg {String} minText Error text to display if the minimum value validation fails (defaults to 'The minimum
98280      * value for this field is {minValue}')
98281      */
98282     minText : 'The minimum value for this field is {0}',
98283
98284     /**
98285      * @cfg {String} maxText Error text to display if the maximum value validation fails (defaults to 'The maximum
98286      * value for this field is {maxValue}')
98287      */
98288     maxText : 'The maximum value for this field is {0}',
98289
98290     /**
98291      * @cfg {String} nanText Error text to display if the value is not a valid number.  For example, this can happen
98292      * if a valid character like '.' or '-' is left in the field with no number (defaults to '{value} is not a valid number')
98293      */
98294     nanText : '{0} is not a valid number',
98295
98296     /**
98297      * @cfg {String} negativeText Error text to display if the value is negative and {@link #minValue} is set to
98298      * <tt>0</tt>. This is used instead of the {@link #minText} in that circumstance only.
98299      */
98300     negativeText : 'The value cannot be negative',
98301
98302     /**
98303      * @cfg {String} baseChars The base set of characters to evaluate as valid numbers (defaults to '0123456789').
98304      */
98305     baseChars : '0123456789',
98306
98307     /**
98308      * @cfg {Boolean} autoStripChars True to automatically strip not allowed characters from the field. Defaults to <tt>false</tt>
98309      */
98310     autoStripChars: false,
98311
98312     initComponent: function() {
98313         var me = this,
98314             allowed;
98315
98316         me.callParent();
98317
98318         me.setMinValue(me.minValue);
98319         me.setMaxValue(me.maxValue);
98320
98321         // Build regexes for masking and stripping based on the configured options
98322         if (me.disableKeyFilter !== true) {
98323             allowed = me.baseChars + '';
98324             if (me.allowDecimals) {
98325                 allowed += me.decimalSeparator;
98326             }
98327             if (me.minValue < 0) {
98328                 allowed += '-';
98329             }
98330             allowed = Ext.String.escapeRegex(allowed);
98331             me.maskRe = new RegExp('[' + allowed + ']');
98332             if (me.autoStripChars) {
98333                 me.stripCharsRe = new RegExp('[^' + allowed + ']', 'gi');
98334             }
98335         }
98336     },
98337
98338     /**
98339      * Runs all of Number's validations and returns an array of any errors. Note that this first
98340      * runs Text's validations, so the returned array is an amalgamation of all field errors.
98341      * The additional validations run test that the value is a number, and that it is within the
98342      * configured min and max values.
98343      * @param {Mixed} value The value to get errors for (defaults to the current field value)
98344      * @return {Array} All validation errors for this field
98345      */
98346     getErrors: function(value) {
98347         var me = this,
98348             errors = me.callParent(arguments),
98349             format = Ext.String.format,
98350             num;
98351
98352         value = Ext.isDefined(value) ? value : this.processRawValue(this.getRawValue());
98353
98354         if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
98355              return errors;
98356         }
98357
98358         value = String(value).replace(me.decimalSeparator, '.');
98359
98360         if(isNaN(value)){
98361             errors.push(format(me.nanText, value));
98362         }
98363
98364         num = me.parseValue(value);
98365
98366         if (me.minValue === 0 && num < 0) {
98367             errors.push(this.negativeText);
98368         }
98369         else if (num < me.minValue) {
98370             errors.push(format(me.minText, me.minValue));
98371         }
98372
98373         if (num > me.maxValue) {
98374             errors.push(format(me.maxText, me.maxValue));
98375         }
98376
98377
98378         return errors;
98379     },
98380
98381     rawToValue: function(rawValue) {
98382         return this.fixPrecision(this.parseValue(rawValue)) || rawValue || null;
98383     },
98384
98385     valueToRaw: function(value) {
98386         var me = this,
98387             decimalSeparator = me.decimalSeparator;
98388         value = me.parseValue(value);
98389         value = me.fixPrecision(value);
98390         value = Ext.isNumber(value) ? value : parseFloat(String(value).replace(decimalSeparator, '.'));
98391         value = isNaN(value) ? '' : String(value).replace('.', decimalSeparator);
98392         return value;
98393     },
98394
98395     onChange: function() {
98396         var me = this,
98397             value = me.getValue(),
98398             valueIsNull = value === null;
98399
98400         me.callParent(arguments);
98401
98402         // Update the spinner buttons
98403         me.setSpinUpEnabled(valueIsNull || value < me.maxValue);
98404         me.setSpinDownEnabled(valueIsNull || value > me.minValue);
98405     },
98406
98407     /**
98408      * Replaces any existing {@link #minValue} with the new value.
98409      * @param {Number} value The minimum value
98410      */
98411     setMinValue : function(value) {
98412         this.minValue = Ext.Number.from(value, Number.NEGATIVE_INFINITY);
98413     },
98414
98415     /**
98416      * Replaces any existing {@link #maxValue} with the new value.
98417      * @param {Number} value The maximum value
98418      */
98419     setMaxValue: function(value) {
98420         this.maxValue = Ext.Number.from(value, Number.MAX_VALUE);
98421     },
98422
98423     // private
98424     parseValue : function(value) {
98425         value = parseFloat(String(value).replace(this.decimalSeparator, '.'));
98426         return isNaN(value) ? null : value;
98427     },
98428
98429     /**
98430      * @private
98431      *
98432      */
98433     fixPrecision : function(value) {
98434         var me = this,
98435             nan = isNaN(value),
98436             precision = me.decimalPrecision;
98437
98438         if (nan || !value) {
98439             return nan ? '' : value;
98440         } else if (!me.allowDecimals || precision <= 0) {
98441             precision = 0;
98442         }
98443
98444         return parseFloat(Ext.Number.toFixed(parseFloat(value), precision));
98445     },
98446
98447     beforeBlur : function() {
98448         var me = this,
98449             v = me.parseValue(me.getRawValue());
98450
98451         if (!Ext.isEmpty(v)) {
98452             me.setValue(v);
98453         }
98454     },
98455
98456     onSpinUp: function() {
98457         var me = this;
98458         if (!me.readOnly) {
98459             me.setValue(Ext.Number.constrain(me.getValue() + me.step, me.minValue, me.maxValue));
98460         }
98461     },
98462
98463     onSpinDown: function() {
98464         var me = this;
98465         if (!me.readOnly) {
98466             me.setValue(Ext.Number.constrain(me.getValue() - me.step, me.minValue, me.maxValue));
98467         }
98468     }
98469 });
98470
98471 /**
98472  * @class Ext.toolbar.Paging
98473  * @extends Ext.toolbar.Toolbar
98474  * <p>As the amount of records increases, the time required for the browser to render
98475  * them increases. Paging is used to reduce the amount of data exchanged with the client.
98476  * Note: if there are more records/rows than can be viewed in the available screen area, vertical
98477  * scrollbars will be added.</p>
98478  * <p>Paging is typically handled on the server side (see exception below). The client sends
98479  * parameters to the server side, which the server needs to interpret and then respond with the
98480  * appropriate data.</p>
98481  * <p><b>Ext.toolbar.Paging</b> is a specialized toolbar that is bound to a {@link Ext.data.Store}
98482  * and provides automatic paging control. This Component {@link Ext.data.Store#load load}s blocks
98483  * of data into the <tt>{@link #store}</tt> by passing {@link Ext.data.Store#paramNames paramNames} used for
98484  * paging criteria.</p>
98485  *
98486  * {@img Ext.toolbar.Paging/Ext.toolbar.Paging.png Ext.toolbar.Paging component}
98487  *
98488  * <p>PagingToolbar is typically used as one of the Grid's toolbars:</p>
98489  * <pre><code>
98490  *    var itemsPerPage = 2;   // set the number of items you want per page
98491  *    
98492  *    var store = Ext.create('Ext.data.Store', {
98493  *        id:'simpsonsStore',
98494  *        autoLoad: false,
98495  *        fields:['name', 'email', 'phone'],
98496  *        pageSize: itemsPerPage, // items per page
98497  *        proxy: {
98498  *            type: 'ajax',
98499  *            url: 'pagingstore.js',  // url that will load data with respect to start and limit params
98500  *            reader: {
98501  *                type: 'json',
98502  *                root: 'items',
98503  *                totalProperty: 'total'
98504  *            }
98505  *        }
98506  *    });
98507  *    
98508  *    // specify segment of data you want to load using params
98509  *    store.load({
98510  *        params:{
98511  *            start:0,    
98512  *            limit: itemsPerPage
98513  *        }
98514  *    });
98515  *    
98516  *    Ext.create('Ext.grid.Panel', {
98517  *        title: 'Simpsons',
98518  *        store: store,
98519  *        columns: [
98520  *            {header: 'Name',  dataIndex: 'name'},
98521  *            {header: 'Email', dataIndex: 'email', flex:1},
98522  *            {header: 'Phone', dataIndex: 'phone'}
98523  *        ],
98524  *        width: 400,
98525  *        height: 125,
98526  *        dockedItems: [{
98527  *            xtype: 'pagingtoolbar',
98528  *            store: store,   // same store GridPanel is using
98529  *            dock: 'bottom',
98530  *            displayInfo: true
98531  *        }],
98532  *        renderTo: Ext.getBody()
98533  *    });
98534  * </code></pre>
98535  *
98536  * <p>To use paging, pass the paging requirements to the server when the store is first loaded.</p>
98537  * <pre><code>
98538 store.load({
98539     params: {
98540         // specify params for the first page load if using paging
98541         start: 0,          
98542         limit: myPageSize,
98543         // other params
98544         foo:   'bar'
98545     }
98546 });
98547  * </code></pre>
98548  * 
98549  * <p>If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration:</p>
98550  * <pre><code>
98551 var myStore = new Ext.data.Store({
98552     {@link Ext.data.Store#autoLoad autoLoad}: {start: 0, limit: 25},
98553     ...
98554 });
98555  * </code></pre>
98556  * 
98557  * <p>The packet sent back from the server would have this form:</p>
98558  * <pre><code>
98559 {
98560     "success": true,
98561     "results": 2000, 
98562     "rows": [ // <b>*Note:</b> this must be an Array 
98563         { "id":  1, "name": "Bill", "occupation": "Gardener" },
98564         { "id":  2, "name":  "Ben", "occupation": "Horticulturalist" },
98565         ...
98566         { "id": 25, "name":  "Sue", "occupation": "Botanist" }
98567     ]
98568 }
98569  * </code></pre>
98570  * <p><u>Paging with Local Data</u></p>
98571  * <p>Paging can also be accomplished with local data using extensions:</p>
98572  * <div class="mdetail-params"><ul>
98573  * <li><a href="http://sencha.com/forum/showthread.php?t=71532">Ext.ux.data.PagingStore</a></li>
98574  * <li>Paging Memory Proxy (examples/ux/PagingMemoryProxy.js)</li>
98575  * </ul></div>
98576  * @constructor Create a new PagingToolbar
98577  * @param {Object} config The config object
98578  * @xtype pagingtoolbar
98579  */
98580 Ext.define('Ext.toolbar.Paging', {
98581     extend: 'Ext.toolbar.Toolbar',
98582     alias: 'widget.pagingtoolbar',
98583     alternateClassName: 'Ext.PagingToolbar',
98584     requires: ['Ext.toolbar.TextItem', 'Ext.form.field.Number'],
98585     /**
98586      * @cfg {Ext.data.Store} store
98587      * The {@link Ext.data.Store} the paging toolbar should use as its data source (required).
98588      */
98589     /**
98590      * @cfg {Boolean} displayInfo
98591      * <tt>true</tt> to display the displayMsg (defaults to <tt>false</tt>)
98592      */
98593     displayInfo: false,
98594     /**
98595      * @cfg {Boolean} prependButtons
98596      * <tt>true</tt> to insert any configured <tt>items</tt> <i>before</i> the paging buttons.
98597      * Defaults to <tt>false</tt>.
98598      */
98599     prependButtons: false,
98600     /**
98601      * @cfg {String} displayMsg
98602      * The paging status message to display (defaults to <tt>'Displaying {0} - {1} of {2}'</tt>).
98603      * Note that this string is formatted using the braced numbers <tt>{0}-{2}</tt> as tokens
98604      * that are replaced by the values for start, end and total respectively. These tokens should
98605      * be preserved when overriding this string if showing those values is desired.
98606      */
98607     displayMsg : 'Displaying {0} - {1} of {2}',
98608     /**
98609      * @cfg {String} emptyMsg
98610      * The message to display when no records are found (defaults to 'No data to display')
98611      */
98612     emptyMsg : 'No data to display',
98613     /**
98614      * @cfg {String} beforePageText
98615      * The text displayed before the input item (defaults to <tt>'Page'</tt>).
98616      */
98617     beforePageText : 'Page',
98618     /**
98619      * @cfg {String} afterPageText
98620      * Customizable piece of the default paging text (defaults to <tt>'of {0}'</tt>). Note that
98621      * this string is formatted using <tt>{0}</tt> as a token that is replaced by the number of
98622      * total pages. This token should be preserved when overriding this string if showing the
98623      * total page count is desired.
98624      */
98625     afterPageText : 'of {0}',
98626     /**
98627      * @cfg {String} firstText
98628      * The quicktip text displayed for the first page button (defaults to <tt>'First Page'</tt>).
98629      * <b>Note</b>: quick tips must be initialized for the quicktip to show.
98630      */
98631     firstText : 'First Page',
98632     /**
98633      * @cfg {String} prevText
98634      * The quicktip text displayed for the previous page button (defaults to <tt>'Previous Page'</tt>).
98635      * <b>Note</b>: quick tips must be initialized for the quicktip to show.
98636      */
98637     prevText : 'Previous Page',
98638     /**
98639      * @cfg {String} nextText
98640      * The quicktip text displayed for the next page button (defaults to <tt>'Next Page'</tt>).
98641      * <b>Note</b>: quick tips must be initialized for the quicktip to show.
98642      */
98643     nextText : 'Next Page',
98644     /**
98645      * @cfg {String} lastText
98646      * The quicktip text displayed for the last page button (defaults to <tt>'Last Page'</tt>).
98647      * <b>Note</b>: quick tips must be initialized for the quicktip to show.
98648      */
98649     lastText : 'Last Page',
98650     /**
98651      * @cfg {String} refreshText
98652      * The quicktip text displayed for the Refresh button (defaults to <tt>'Refresh'</tt>).
98653      * <b>Note</b>: quick tips must be initialized for the quicktip to show.
98654      */
98655     refreshText : 'Refresh',
98656     /**
98657      * @cfg {Number} inputItemWidth
98658      * The width in pixels of the input field used to display and change the current page number (defaults to 30).
98659      */
98660     inputItemWidth : 30,
98661     
98662     /**
98663      * Gets the standard paging items in the toolbar
98664      * @private
98665      */
98666     getPagingItems: function() {
98667         var me = this;
98668         
98669         return [{
98670             itemId: 'first',
98671             tooltip: me.firstText,
98672             overflowText: me.firstText,
98673             iconCls: Ext.baseCSSPrefix + 'tbar-page-first',
98674             disabled: true,
98675             handler: me.moveFirst,
98676             scope: me
98677         },{
98678             itemId: 'prev',
98679             tooltip: me.prevText,
98680             overflowText: me.prevText,
98681             iconCls: Ext.baseCSSPrefix + 'tbar-page-prev',
98682             disabled: true,
98683             handler: me.movePrevious,
98684             scope: me
98685         },
98686         '-',
98687         me.beforePageText,
98688         {
98689             xtype: 'numberfield',
98690             itemId: 'inputItem',
98691             name: 'inputItem',
98692             cls: Ext.baseCSSPrefix + 'tbar-page-number',
98693             allowDecimals: false,
98694             minValue: 1,
98695             hideTrigger: true,
98696             enableKeyEvents: true,
98697             selectOnFocus: true,
98698             submitValue: false,
98699             width: me.inputItemWidth,
98700             margins: '-1 2 3 2',
98701             listeners: {
98702                 scope: me,
98703                 keydown: me.onPagingKeyDown,
98704                 blur: me.onPagingBlur
98705             }
98706         },{
98707             xtype: 'tbtext',
98708             itemId: 'afterTextItem',
98709             text: Ext.String.format(me.afterPageText, 1)
98710         },
98711         '-',
98712         {
98713             itemId: 'next',
98714             tooltip: me.nextText,
98715             overflowText: me.nextText,
98716             iconCls: Ext.baseCSSPrefix + 'tbar-page-next',
98717             disabled: true,
98718             handler: me.moveNext,
98719             scope: me
98720         },{
98721             itemId: 'last',
98722             tooltip: me.lastText,
98723             overflowText: me.lastText,
98724             iconCls: Ext.baseCSSPrefix + 'tbar-page-last',
98725             disabled: true,
98726             handler: me.moveLast,
98727             scope: me
98728         },
98729         '-',
98730         {
98731             itemId: 'refresh',
98732             tooltip: me.refreshText,
98733             overflowText: me.refreshText,
98734             iconCls: Ext.baseCSSPrefix + 'tbar-loading',
98735             handler: me.doRefresh,
98736             scope: me
98737         }];
98738     },
98739
98740     initComponent : function(){
98741         var me = this,
98742             pagingItems = me.getPagingItems(),
98743             userItems   = me.items || me.buttons || [];
98744             
98745         if (me.prependButtons) {
98746             me.items = userItems.concat(pagingItems);
98747         } else {
98748             me.items = pagingItems.concat(userItems);
98749         }
98750         delete me.buttons;
98751         
98752         if (me.displayInfo) {
98753             me.items.push('->');
98754             me.items.push({xtype: 'tbtext', itemId: 'displayItem'});
98755         }
98756         
98757         me.callParent();
98758         
98759         me.addEvents(
98760             /**
98761              * @event change
98762              * Fires after the active page has been changed.
98763              * @param {Ext.toolbar.Paging} this
98764              * @param {Object} pageData An object that has these properties:<ul>
98765              * <li><code>total</code> : Number <div class="sub-desc">The total number of records in the dataset as
98766              * returned by the server</div></li>
98767              * <li><code>currentPage</code> : Number <div class="sub-desc">The current page number</div></li>
98768              * <li><code>pageCount</code> : Number <div class="sub-desc">The total number of pages (calculated from
98769              * the total number of records in the dataset as returned by the server and the current {@link #pageSize})</div></li>
98770              * <li><code>toRecord</code> : Number <div class="sub-desc">The starting record index for the current page</div></li>
98771              * <li><code>fromRecord</code> : Number <div class="sub-desc">The ending record index for the current page</div></li>
98772              * </ul>
98773              */
98774             'change',
98775             /**
98776              * @event beforechange
98777              * Fires just before the active page is changed.
98778              * Return false to prevent the active page from being changed.
98779              * @param {Ext.toolbar.Paging} this
98780              * @param {Number} page The page number that will be loaded on change 
98781              */
98782             'beforechange'
98783         );
98784         me.on('afterlayout', me.onLoad, me, {single: true});
98785
98786         me.bindStore(me.store, true);
98787     },
98788     // private
98789     updateInfo : function(){
98790         var me = this,
98791             displayItem = me.child('#displayItem'),
98792             store = me.store,
98793             pageData = me.getPageData(),
98794             count, msg;
98795
98796         if (displayItem) {
98797             count = store.getCount();
98798             if (count === 0) {
98799                 msg = me.emptyMsg;
98800             } else {
98801                 msg = Ext.String.format(
98802                     me.displayMsg,
98803                     pageData.fromRecord,
98804                     pageData.toRecord,
98805                     pageData.total
98806                 );
98807             }
98808             displayItem.setText(msg);
98809             me.doComponentLayout();
98810         }
98811     },
98812
98813     // private
98814     onLoad : function(){
98815         var me = this,
98816             pageData,
98817             currPage,
98818             pageCount,
98819             afterText;
98820             
98821         if (!me.rendered) {
98822             return;
98823         }
98824
98825         pageData = me.getPageData();
98826         currPage = pageData.currentPage;
98827         pageCount = pageData.pageCount;
98828         afterText = Ext.String.format(me.afterPageText, isNaN(pageCount) ? 1 : pageCount);
98829
98830         me.child('#afterTextItem').setText(afterText);
98831         me.child('#inputItem').setValue(currPage);
98832         me.child('#first').setDisabled(currPage === 1);
98833         me.child('#prev').setDisabled(currPage === 1);
98834         me.child('#next').setDisabled(currPage === pageCount);
98835         me.child('#last').setDisabled(currPage === pageCount);
98836         me.child('#refresh').enable();
98837         me.updateInfo();
98838         me.fireEvent('change', me, pageData);
98839     },
98840
98841     // private
98842     getPageData : function(){
98843         var store = this.store,
98844             totalCount = store.getTotalCount();
98845             
98846         return {
98847             total : totalCount,
98848             currentPage : store.currentPage,
98849             pageCount: Math.ceil(totalCount / store.pageSize),
98850             //pageCount :  store.getPageCount(),
98851             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
98852             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
98853             
98854         };
98855     },
98856
98857     // private
98858     onLoadError : function(){
98859         if (!this.rendered) {
98860             return;
98861         }
98862         this.child('#refresh').enable();
98863     },
98864
98865     // private
98866     readPageFromInput : function(pageData){
98867         var v = this.child('#inputItem').getValue(),
98868             pageNum = parseInt(v, 10);
98869             
98870         if (!v || isNaN(pageNum)) {
98871             this.child('#inputItem').setValue(pageData.currentPage);
98872             return false;
98873         }
98874         return pageNum;
98875     },
98876
98877     onPagingFocus : function(){
98878         this.child('#inputItem').select();
98879     },
98880
98881     //private
98882     onPagingBlur : function(e){
98883         var curPage = this.getPageData().currentPage;
98884         this.child('#inputItem').setValue(curPage);
98885     },
98886
98887     // private
98888     onPagingKeyDown : function(field, e){
98889         var k = e.getKey(),
98890             pageData = this.getPageData(),
98891             increment = e.shiftKey ? 10 : 1,
98892             pageNum,
98893             me = this;
98894
98895         if (k == e.RETURN) {
98896             e.stopEvent();
98897             pageNum = me.readPageFromInput(pageData);
98898             if (pageNum !== false) {
98899                 pageNum = Math.min(Math.max(1, pageNum), pageData.total);
98900                 if(me.fireEvent('beforechange', me, pageNum) !== false){
98901                     me.store.loadPage(pageNum);
98902                 }
98903             }
98904         } else if (k == e.HOME || k == e.END) {
98905             e.stopEvent();
98906             pageNum = k == e.HOME ? 1 : pageData.pageCount;
98907             field.setValue(pageNum);
98908         } else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN) {
98909             e.stopEvent();
98910             pageNum = me.readPageFromInput(pageData);
98911             if (pageNum) {
98912                 if (k == e.DOWN || k == e.PAGEDOWN) {
98913                     increment *= -1;
98914                 }
98915                 pageNum += increment;
98916                 if (pageNum >= 1 && pageNum <= pageData.pages) {
98917                     field.setValue(pageNum);
98918                 }
98919             }
98920         }
98921     },
98922
98923     // private
98924     beforeLoad : function(){
98925         if(this.rendered && this.refresh){
98926             this.refresh.disable();
98927         }
98928     },
98929
98930     // private
98931     doLoad : function(start){
98932         if(this.fireEvent('beforechange', this, o) !== false){
98933             this.store.load();
98934         }
98935     },
98936
98937     /**
98938      * Move to the first page, has the same effect as clicking the 'first' button.
98939      */
98940     moveFirst : function(){
98941         var me = this;
98942         if(me.fireEvent('beforechange', me, 1) !== false){
98943             me.store.loadPage(1);
98944         }
98945     },
98946
98947     /**
98948      * Move to the previous page, has the same effect as clicking the 'previous' button.
98949      */
98950     movePrevious : function(){
98951         var me = this,
98952             prev = me.store.currentPage - 1;
98953         
98954         if(me.fireEvent('beforechange', me, prev) !== false){
98955             me.store.previousPage();
98956         }
98957     },
98958
98959     /**
98960      * Move to the next page, has the same effect as clicking the 'next' button.
98961      */
98962     moveNext : function(){
98963         var me = this;        
98964         if(me.fireEvent('beforechange', me, me.store.currentPage + 1) !== false){
98965             me.store.nextPage();
98966         }
98967     },
98968
98969     /**
98970      * Move to the last page, has the same effect as clicking the 'last' button.
98971      */
98972     moveLast : function(){
98973         var me = this, 
98974             last = this.getPageData().pageCount;
98975         
98976         if(me.fireEvent('beforechange', me, last) !== false){
98977             me.store.loadPage(last);
98978         }
98979     },
98980
98981     /**
98982      * Refresh the current page, has the same effect as clicking the 'refresh' button.
98983      */
98984     doRefresh : function(){
98985         var me = this,
98986             current = me.store.currentPage;
98987         
98988         if(me.fireEvent('beforechange', me, current) !== false){
98989             me.store.loadPage(current);
98990         }
98991     },
98992
98993     /**
98994      * Binds the paging toolbar to the specified {@link Ext.data.Store}
98995      * @param {Store} store The store to bind to this toolbar
98996      * @param {Boolean} initial (Optional) true to not remove listeners
98997      */
98998     bindStore : function(store, initial){
98999         var me = this;
99000         
99001         if (!initial && me.store) {
99002             if(store !== me.store && me.store.autoDestroy){
99003                 me.store.destroy();
99004             }else{
99005                 me.store.un('beforeload', me.beforeLoad, me);
99006                 me.store.un('load', me.onLoad, me);
99007                 me.store.un('exception', me.onLoadError, me);
99008             }
99009             if(!store){
99010                 me.store = null;
99011             }
99012         }
99013         if (store) {
99014             store = Ext.data.StoreManager.lookup(store);
99015             store.on({
99016                 scope: me,
99017                 beforeload: me.beforeLoad,
99018                 load: me.onLoad,
99019                 exception: me.onLoadError
99020             });
99021         }
99022         me.store = store;
99023     },
99024
99025     /**
99026      * Unbinds the paging toolbar from the specified {@link Ext.data.Store} <b>(deprecated)</b>
99027      * @param {Ext.data.Store} store The data store to unbind
99028      */
99029     unbind : function(store){
99030         this.bindStore(null);
99031     },
99032
99033     /**
99034      * Binds the paging toolbar to the specified {@link Ext.data.Store} <b>(deprecated)</b>
99035      * @param {Ext.data.Store} store The data store to bind
99036      */
99037     bind : function(store){
99038         this.bindStore(store);
99039     },
99040
99041     // private
99042     onDestroy : function(){
99043         this.bindStore(null);
99044         this.callParent();
99045     }
99046 });
99047
99048 /**
99049  * @class Ext.view.BoundList
99050  * @extends Ext.view.View
99051  * An internal used DataView for ComboBox, MultiSelect and ItemSelector.
99052  */
99053 Ext.define('Ext.view.BoundList', {
99054     extend: 'Ext.view.View',
99055     alias: 'widget.boundlist',
99056     alternateClassName: 'Ext.BoundList',
99057     requires: ['Ext.layout.component.BoundList', 'Ext.toolbar.Paging'],
99058
99059     /**
99060      * @cfg {Number} pageSize If greater than <tt>0</tt>, a {@link Ext.toolbar.Paging} is displayed at the
99061      * bottom of the list and store queries will execute with page start and
99062      * {@link Ext.toolbar.Paging#pageSize limit} parameters.
99063      */
99064     pageSize: 0,
99065
99066     /**
99067      * @property pagingToolbar
99068      * @type {Ext.toolbar.Paging}
99069      * A reference to the PagingToolbar instance in this view. Only populated if {@link #pageSize} is greater
99070      * than zero and the BoundList has been rendered.
99071      */
99072
99073     // private overrides
99074     autoScroll: true,
99075     baseCls: Ext.baseCSSPrefix + 'boundlist',
99076     listItemCls: '',
99077     shadow: false,
99078     trackOver: true,
99079     refreshed: 0,
99080
99081     ariaRole: 'listbox',
99082
99083     componentLayout: 'boundlist',
99084
99085     renderTpl: ['<div class="list-ct"></div>'],
99086
99087     initComponent: function() {
99088         var me = this,
99089             baseCls = me.baseCls,
99090             itemCls = baseCls + '-item';
99091         me.itemCls = itemCls;
99092         me.selectedItemCls = baseCls + '-selected';
99093         me.overItemCls = baseCls + '-item-over';
99094         me.itemSelector = "." + itemCls;
99095
99096         if (me.floating) {
99097             me.addCls(baseCls + '-floating');
99098         }
99099
99100         // should be setting aria-posinset based on entire set of data
99101         // not filtered set
99102         me.tpl = Ext.create('Ext.XTemplate', 
99103             '<ul><tpl for=".">',
99104                 '<li role="option" class="' + itemCls + '">' + me.getInnerTpl(me.displayField) + '</li>',
99105             '</tpl></ul>'
99106         );
99107
99108         if (me.pageSize) {
99109             me.pagingToolbar = me.createPagingToolbar();
99110         }
99111
99112         me.callParent();
99113
99114         Ext.applyIf(me.renderSelectors, {
99115             listEl: '.list-ct'
99116         });
99117     },
99118
99119     createPagingToolbar: function() {
99120         return Ext.widget('pagingtoolbar', {
99121             pageSize: this.pageSize,
99122             store: this.store,
99123             border: false
99124         });
99125     },
99126
99127     onRender: function() {
99128         var me = this,
99129             toolbar = me.pagingToolbar;
99130         me.callParent(arguments);
99131         if (toolbar) {
99132             toolbar.render(me.el);
99133         }
99134     },
99135
99136     bindStore : function(store, initial) {
99137         var me = this,
99138             toolbar = me.pagingToolbar;
99139         me.callParent(arguments);
99140         if (toolbar) {
99141             toolbar.bindStore(store, initial);
99142         }
99143     },
99144
99145     getTargetEl: function() {
99146         return this.listEl || this.el;
99147     },
99148
99149     getInnerTpl: function(displayField) {
99150         return '{' + displayField + '}';
99151     },
99152
99153     refresh: function() {
99154         var me = this;
99155         me.callParent();
99156         if (me.isVisible()) {
99157             me.refreshed++;
99158             me.doComponentLayout();
99159             me.refreshed--;
99160         }
99161     },
99162     
99163     initAria: function() {
99164         this.callParent();
99165         
99166         var selModel = this.getSelectionModel(),
99167             mode     = selModel.getSelectionMode(),
99168             actionEl = this.getActionEl();
99169         
99170         // TODO: subscribe to mode changes or allow the selModel to manipulate this attribute.
99171         if (mode !== 'SINGLE') {
99172             actionEl.dom.setAttribute('aria-multiselectable', true);
99173         }
99174     },
99175
99176     onDestroy: function() {
99177         Ext.destroyMembers(this, 'pagingToolbar', 'listEl');
99178         this.callParent();
99179     }
99180 });
99181
99182 /**
99183  * @class Ext.view.BoundListKeyNav
99184  * @extends Ext.util.KeyNav
99185  * A specialized {@link Ext.util.KeyNav} implementation for navigating a {@link Ext.view.BoundList} using
99186  * the keyboard. The up, down, pageup, pagedown, home, and end keys move the active highlight
99187  * through the list. The enter key invokes the selection model's select action using the highlighted item.
99188  */
99189 Ext.define('Ext.view.BoundListKeyNav', {
99190     extend: 'Ext.util.KeyNav',
99191     requires: 'Ext.view.BoundList',
99192
99193     /**
99194      * @cfg {Ext.view.BoundList} boundList
99195      * @required
99196      * The {@link Ext.view.BoundList} instance for which key navigation will be managed. This is required.
99197      */
99198
99199     constructor: function(el, config) {
99200         var me = this;
99201         me.boundList = config.boundList;
99202         me.callParent([el, Ext.apply({}, config, me.defaultHandlers)]);
99203     },
99204
99205     defaultHandlers: {
99206         up: function() {
99207             var me = this,
99208                 boundList = me.boundList,
99209                 allItems = boundList.all,
99210                 oldItem = boundList.highlightedItem,
99211                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
99212                 newItemIdx = oldItemIdx > 0 ? oldItemIdx - 1 : allItems.getCount() - 1; //wraps around
99213             me.highlightAt(newItemIdx);
99214         },
99215
99216         down: function() {
99217             var me = this,
99218                 boundList = me.boundList,
99219                 allItems = boundList.all,
99220                 oldItem = boundList.highlightedItem,
99221                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
99222                 newItemIdx = oldItemIdx < allItems.getCount() - 1 ? oldItemIdx + 1 : 0; //wraps around
99223             me.highlightAt(newItemIdx);
99224         },
99225
99226         pageup: function() {
99227             //TODO
99228         },
99229
99230         pagedown: function() {
99231             //TODO
99232         },
99233
99234         home: function() {
99235             this.highlightAt(0);
99236         },
99237
99238         end: function() {
99239             var me = this;
99240             me.highlightAt(me.boundList.all.getCount() - 1);
99241         },
99242
99243         enter: function(e) {
99244             this.selectHighlighted(e);
99245         }
99246     },
99247
99248     /**
99249      * Highlights the item at the given index.
99250      * @param {Number} index
99251      */
99252     highlightAt: function(index) {
99253         var boundList = this.boundList,
99254             item = boundList.all.item(index);
99255         if (item) {
99256             item = item.dom;
99257             boundList.highlightItem(item);
99258             boundList.getTargetEl().scrollChildIntoView(item, false);
99259         }
99260     },
99261
99262     /**
99263      * Triggers selection of the currently highlighted item according to the behavior of
99264      * the configured SelectionModel.
99265      */
99266     selectHighlighted: function(e) {
99267         var me = this,
99268             boundList = me.boundList,
99269             highlighted = boundList.highlightedItem,
99270             selModel = boundList.getSelectionModel();
99271         if (highlighted) {
99272             selModel.selectWithEvent(boundList.getRecord(highlighted), e);
99273         }
99274     }
99275
99276 });
99277 /**
99278  * @class Ext.form.field.ComboBox
99279  * @extends Ext.form.field.Picker
99280  *
99281  * A combobox control with support for autocomplete, remote loading, and many other features.
99282  *
99283  * A ComboBox is like a combination of a traditional HTML text `<input>` field and a `<select>`
99284  * field; the user is able to type freely into the field, and/or pick values from a dropdown selection
99285  * list. The user can input any value by default, even if it does not appear in the selection list;
99286  * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`.
99287  *
99288  * The selection list's options are populated from any {@link Ext.data.Store}, including remote
99289  * stores. The data items in the store are mapped to each option's displayed text and backing value via
99290  * the {@link #valueField} and {@link #displayField} configurations, respectively.
99291  *
99292  * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be
99293  * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user.
99294  *
99295  * {@img Ext.form.ComboBox/Ext.form.ComboBox.png Ext.form.ComboBox component}
99296  *
99297  * ## Example usage:
99298  *
99299  *     // The data store containing the list of states
99300  *     var states = Ext.create('Ext.data.Store', {
99301  *         fields: ['abbr', 'name'],
99302  *         data : [
99303  *             {"abbr":"AL", "name":"Alabama"},
99304  *             {"abbr":"AK", "name":"Alaska"},
99305  *             {"abbr":"AZ", "name":"Arizona"}
99306  *             //...
99307  *         ]
99308  *     });
99309  *
99310  *     // Create the combo box, attached to the states data store
99311  *     Ext.create('Ext.form.ComboBox', {
99312  *         fieldLabel: 'Choose State',
99313  *         store: states,
99314  *         queryMode: 'local',
99315  *         displayField: 'name',
99316  *         valueField: 'abbr',
99317  *         renderTo: Ext.getBody()
99318  *     });
99319  *
99320  * ## Events
99321  *
99322  * To do something when something in ComboBox is selected, configure the select event:
99323  *
99324  *     var cb = Ext.create('Ext.form.ComboBox', {
99325  *         // all of your config options
99326  *         listeners:{
99327  *              scope: yourScope,
99328  *              'select': yourFunction
99329  *         }
99330  *     });
99331  *
99332  *     // Alternatively, you can assign events after the object is created:
99333  *     var cb = new Ext.form.field.ComboBox(yourOptions);
99334  *     cb.on('select', yourFunction, yourScope);
99335  *
99336  * ## Multiple Selection
99337  *
99338  * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
99339  * {@link #multiSelect} config to `true`.
99340  *
99341  * @constructor
99342  * Create a new ComboBox.
99343  * @param {Object} config Configuration options
99344  * @xtype combo
99345  * @docauthor Jason Johnston <jason@sencha.com>
99346  */
99347 Ext.define('Ext.form.field.ComboBox', {
99348     extend:'Ext.form.field.Picker',
99349     requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager'],
99350     alternateClassName: 'Ext.form.ComboBox',
99351     alias: ['widget.combobox', 'widget.combo'],
99352
99353     /**
99354      * @cfg {String} triggerCls
99355      * An additional CSS class used to style the trigger button. The trigger will always get the
99356      * {@link #triggerBaseCls} by default and <tt>triggerCls</tt> will be <b>appended</b> if specified.
99357      * Defaults to 'x-form-arrow-trigger' for ComboBox.
99358      */
99359     triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
99360
99361     /**
99362      * @cfg {Ext.data.Store/Array} store The data source to which this combo is bound (defaults to <tt>undefined</tt>).
99363      * Acceptable values for this property are:
99364      * <div class="mdetail-params"><ul>
99365      * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
99366      * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.Store} internally,
99367      * automatically generating {@link Ext.data.Field#name field names} to work with all data components.
99368      * <div class="mdetail-params"><ul>
99369      * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
99370      * A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
99371      * {@link #valueField} and {@link #displayField})</div></li>
99372      * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">
99373      * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
99374      * {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
99375      * </div></li></ul></div></li></ul></div>
99376      * <p>See also <tt>{@link #queryMode}</tt>.</p>
99377      */
99378
99379     /**
99380      * @cfg {Boolean} multiSelect
99381      * If set to <tt>true</tt>, allows the combo field to hold more than one value at a time, and allows selecting
99382      * multiple items from the dropdown list. The combo's text field will show all selected values separated by
99383      * the {@link #delimiter}. (Defaults to <tt>false</tt>.)
99384      */
99385     multiSelect: false,
99386
99387     /**
99388      * @cfg {String} delimiter
99389      * The character(s) used to separate the {@link #displayField display values} of multiple selected items
99390      * when <tt>{@link #multiSelect} = true</tt>. Defaults to <tt>', '</tt>.
99391      */
99392     delimiter: ', ',
99393
99394     /**
99395      * @cfg {String} displayField The underlying {@link Ext.data.Field#name data field name} to bind to this
99396      * ComboBox (defaults to 'text').
99397      * <p>See also <tt>{@link #valueField}</tt>.</p>
99398      */
99399     displayField: 'text',
99400
99401     /**
99402      * @cfg {String} valueField
99403      * @required
99404      * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox (defaults to match
99405      * the value of the {@link #displayField} config).
99406      * <p><b>Note</b>: use of a <tt>valueField</tt> requires the user to make a selection in order for a value to be
99407      * mapped. See also <tt>{@link #displayField}</tt>.</p>
99408      */
99409
99410     /**
99411      * @cfg {String} triggerAction The action to execute when the trigger is clicked.
99412      * <div class="mdetail-params"><ul>
99413      * <li><b><tt>'all'</tt></b> : <b>Default</b>
99414      * <p class="sub-desc">{@link #doQuery run the query} specified by the <tt>{@link #allQuery}</tt> config option</p></li>
99415      * <li><b><tt>'query'</tt></b> :
99416      * <p class="sub-desc">{@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.</p></li>
99417      * </ul></div>
99418      * <p>See also <code>{@link #queryParam}</code>.</p>
99419      */
99420     triggerAction: 'all',
99421
99422     /**
99423      * @cfg {String} allQuery The text query to send to the server to return all records for the list
99424      * with no filtering (defaults to '')
99425      */
99426     allQuery: '',
99427
99428     /**
99429      * @cfg {String} queryParam Name of the query ({@link Ext.data.proxy.Proxy#extraParam extraParam} name for the store)
99430      * as it will be passed on the querystring (defaults to <tt>'query'</tt>). If explicitly set to a falsey value it will
99431      * not be send.
99432      */
99433     queryParam: 'query',
99434
99435     /**
99436      * @cfg {String} queryMode
99437      * The mode for queries. Acceptable values are:
99438      * <div class="mdetail-params"><ul>
99439      * <li><b><tt>'remote'</tt></b> : <b>Default</b>
99440      * <p class="sub-desc">Automatically loads the <tt>{@link #store}</tt> the <b>first</b> time the trigger
99441      * is clicked. If you do not want the store to be automatically loaded the first time the trigger is
99442      * clicked, set to <tt>'local'</tt> and manually load the store.  To force a requery of the store
99443      * <b>every</b> time the trigger is clicked see <tt>{@link #lastQuery}</tt>.</p></li>
99444      * <li><b><tt>'local'</tt></b> :
99445      * <p class="sub-desc">ComboBox loads local data</p>
99446      * <pre><code>
99447 var combo = new Ext.form.field.ComboBox({
99448     renderTo: document.body,
99449     queryMode: 'local',
99450     store: new Ext.data.ArrayStore({
99451         id: 0,
99452         fields: [
99453             'myId',  // numeric value is the key
99454             'displayText'
99455         ],
99456         data: [[1, 'item1'], [2, 'item2']]  // data is local
99457     }),
99458     valueField: 'myId',
99459     displayField: 'displayText',
99460     triggerAction: 'all'
99461 });
99462      * </code></pre></li>
99463      * </ul></div>
99464      */
99465     queryMode: 'remote',
99466
99467     queryCaching: true,
99468
99469     /**
99470      * @cfg {Number} pageSize If greater than <tt>0</tt>, a {@link Ext.toolbar.Paging} is displayed in the
99471      * footer of the dropdown list and the {@link #doQuery filter queries} will execute with page start and
99472      * {@link Ext.toolbar.Paging#pageSize limit} parameters. Only applies when <tt>{@link #queryMode} = 'remote'</tt>
99473      * (defaults to <tt>0</tt>).
99474      */
99475     pageSize: 0,
99476
99477     /**
99478      * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and
99479      * sending the query to filter the dropdown list (defaults to <tt>500</tt> if <tt>{@link #queryMode} = 'remote'</tt>
99480      * or <tt>10</tt> if <tt>{@link #queryMode} = 'local'</tt>)
99481      */
99482
99483     /**
99484      * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and
99485      * {@link #typeAhead} activate (defaults to <tt>4</tt> if <tt>{@link #queryMode} = 'remote'</tt> or <tt>0</tt> if
99486      * <tt>{@link #queryMode} = 'local'</tt>, does not apply if <tt>{@link Ext.form.field.Trigger#editable editable} = false</tt>).
99487      */
99488
99489     /**
99490      * @cfg {Boolean} autoSelect <tt>true</tt> to automatically highlight the first result gathered by the data store
99491      * in the dropdown list when it is opened. (Defaults to <tt>true</tt>). A false value would cause nothing in the
99492      * list to be highlighted automatically, so the user would have to manually highlight an item before pressing
99493      * the enter or {@link #selectOnTab tab} key to select it (unless the value of ({@link #typeAhead}) were true),
99494      * or use the mouse to select a value.
99495      */
99496     autoSelect: true,
99497
99498     /**
99499      * @cfg {Boolean} typeAhead <tt>true</tt> to populate and autoselect the remainder of the text being
99500      * typed after a configurable delay ({@link #typeAheadDelay}) if it matches a known value (defaults
99501      * to <tt>false</tt>)
99502      */
99503     typeAhead: false,
99504
99505     /**
99506      * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
99507      * if <tt>{@link #typeAhead} = true</tt> (defaults to <tt>250</tt>)
99508      */
99509     typeAheadDelay: 250,
99510
99511     /**
99512      * @cfg {Boolean} selectOnTab
99513      * Whether the Tab key should select the currently highlighted item. Defaults to <tt>true</tt>.
99514      */
99515     selectOnTab: true,
99516
99517     /**
99518      * @cfg {Boolean} forceSelection <tt>true</tt> to restrict the selected value to one of the values in the list,
99519      * <tt>false</tt> to allow the user to set arbitrary text into the field (defaults to <tt>false</tt>)
99520      */
99521     forceSelection: false,
99522
99523     /**
99524      * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
99525      * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined). If this
99526      * default text is used, it means there is no value set and no validation will occur on this field.
99527      */
99528
99529     /**
99530      * The value of the match string used to filter the store. Delete this property to force a requery.
99531      * Example use:
99532      * <pre><code>
99533 var combo = new Ext.form.field.ComboBox({
99534     ...
99535     queryMode: 'remote',
99536     listeners: {
99537         // delete the previous query in the beforequery event or set
99538         // combo.lastQuery = null (this will reload the store the next time it expands)
99539         beforequery: function(qe){
99540             delete qe.combo.lastQuery;
99541         }
99542     }
99543 });
99544      * </code></pre>
99545      * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used
99546      * configure the combo with <tt>lastQuery=''</tt>. Example use:
99547      * <pre><code>
99548 var combo = new Ext.form.field.ComboBox({
99549     ...
99550     queryMode: 'local',
99551     triggerAction: 'all',
99552     lastQuery: ''
99553 });
99554      * </code></pre>
99555      * @property lastQuery
99556      * @type String
99557      */
99558
99559     /**
99560      * @cfg {Object} defaultListConfig
99561      * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.
99562      */
99563     defaultListConfig: {
99564         emptyText: '',
99565         loadingText: 'Loading...',
99566         loadingHeight: 70,
99567         minWidth: 70,
99568         maxHeight: 300,
99569         shadow: 'sides'
99570     },
99571
99572     /**
99573      * @cfg {Mixed} transform
99574      * The id, DOM node or {@link Ext.core.Element} of an existing HTML <tt>&lt;select&gt;</tt> element to
99575      * convert into a ComboBox. The target select's options will be used to build the options in the ComboBox
99576      * dropdown; a configured {@link #store} will take precedence over this.
99577      */
99578
99579     /**
99580      * @cfg {Object} listConfig
99581      * <p>An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s
99582      * constructor. Any configuration that is valid for BoundList can be included. Some of the more useful
99583      * ones are:</p>
99584      * <ul>
99585      *     <li>{@link Ext.view.BoundList#cls} - defaults to empty</li>
99586      *     <li>{@link Ext.view.BoundList#emptyText} - defaults to empty string</li>
99587      *     <li>{@link Ext.view.BoundList#getInnerTpl} - defaults to the template defined in BoundList</li>
99588      *     <li>{@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList</li>
99589      *     <li>{@link Ext.view.BoundList#loadingText} - defaults to <tt>'Loading...'</tt></li>
99590      *     <li>{@link Ext.view.BoundList#minWidth} - defaults to <tt>70</tt></li>
99591      *     <li>{@link Ext.view.BoundList#maxWidth} - defaults to <tt>undefined</tt></li>
99592      *     <li>{@link Ext.view.BoundList#maxHeight} - defaults to <tt>300</tt></li>
99593      *     <li>{@link Ext.view.BoundList#resizable} - defaults to <tt>false</tt></li>
99594      *     <li>{@link Ext.view.BoundList#shadow} - defaults to <tt>'sides'</tt></li>
99595      *     <li>{@link Ext.view.BoundList#width} - defaults to <tt>undefined</tt> (automatically set to the width
99596      *         of the ComboBox field if {@link #matchFieldWidth} is true)</li>
99597      * </ul>
99598      */
99599
99600     //private
99601     ignoreSelection: 0,
99602
99603     initComponent: function() {
99604         var me = this,
99605             isDefined = Ext.isDefined,
99606             store = me.store,
99607             transform = me.transform,
99608             transformSelect, isLocalMode;
99609
99610         if (!store && !transform) {
99611             Ext.Error.raise('Either a valid store, or a HTML select to transform, must be configured on the combo.');
99612         }
99613         if (me.typeAhead && me.multiSelect) {
99614             Ext.Error.raise('typeAhead and multiSelect are mutually exclusive options -- please remove one of them.');
99615         }
99616         if (me.typeAhead && !me.editable) {
99617             Ext.Error.raise('If typeAhead is enabled the combo must be editable: true -- please change one of those settings.');
99618         }
99619         if (me.selectOnFocus && !me.editable) {
99620             Ext.Error.raise('If selectOnFocus is enabled the combo must be editable: true -- please change one of those settings.');
99621         }
99622
99623         this.addEvents(
99624             // TODO need beforeselect?
99625
99626             /**
99627              * @event beforequery
99628              * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's
99629              * cancel property to true.
99630              * @param {Object} queryEvent An object that has these properties:<ul>
99631              * <li><code>combo</code> : Ext.form.field.ComboBox <div class="sub-desc">This combo box</div></li>
99632              * <li><code>query</code> : String <div class="sub-desc">The query string</div></li>
99633              * <li><code>forceAll</code> : Boolean <div class="sub-desc">True to force "all" query</div></li>
99634              * <li><code>cancel</code> : Boolean <div class="sub-desc">Set to true to cancel the query</div></li>
99635              * </ul>
99636              */
99637             'beforequery',
99638
99639             /*
99640              * @event select
99641              * Fires when at least one list item is selected.
99642              * @param {Ext.form.field.ComboBox} combo This combo box
99643              * @param {Array} records The selected records
99644              */
99645             'select'
99646         );
99647
99648         // Build store from 'transform' HTML select element's options
99649         if (!store && transform) {
99650             transformSelect = Ext.getDom(transform);
99651             if (transformSelect) {
99652                 store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option) {
99653                     return [option.value, option.text];
99654                 });
99655                 if (!me.name) {
99656                     me.name = transformSelect.name;
99657                 }
99658                 if (!('value' in me)) {
99659                     me.value = transformSelect.value;
99660                 }
99661             }
99662         }
99663
99664         me.bindStore(store, true);
99665         store = me.store;
99666         if (store.autoCreated) {
99667             me.queryMode = 'local';
99668             me.valueField = me.displayField = 'field1';
99669             if (!store.expanded) {
99670                 me.displayField = 'field2';
99671             }
99672         }
99673
99674
99675         if (!isDefined(me.valueField)) {
99676             me.valueField = me.displayField;
99677         }
99678
99679         isLocalMode = me.queryMode === 'local';
99680         if (!isDefined(me.queryDelay)) {
99681             me.queryDelay = isLocalMode ? 10 : 500;
99682         }
99683         if (!isDefined(me.minChars)) {
99684             me.minChars = isLocalMode ? 0 : 4;
99685         }
99686
99687         if (!me.displayTpl) {
99688             me.displayTpl = Ext.create('Ext.XTemplate',
99689                 '<tpl for=".">' +
99690                     '{[typeof values === "string" ? values : values.' + me.displayField + ']}' +
99691                     '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
99692                 '</tpl>'
99693             );
99694         } else if (Ext.isString(me.displayTpl)) {
99695             me.displayTpl = Ext.create('Ext.XTemplate', me.displayTpl);
99696         }
99697
99698         me.callParent();
99699
99700         me.doQueryTask = Ext.create('Ext.util.DelayedTask', me.doRawQuery, me);
99701
99702         // store has already been loaded, setValue
99703         if (me.store.getCount() > 0) {
99704             me.setValue(me.value);
99705         }
99706
99707         // render in place of 'transform' select
99708         if (transformSelect) {
99709             me.render(transformSelect.parentNode, transformSelect);
99710             Ext.removeNode(transformSelect);
99711             delete me.renderTo;
99712         }
99713     },
99714
99715     beforeBlur: function() {
99716         var me = this;
99717         me.doQueryTask.cancel();
99718         if (me.forceSelection) {
99719             me.assertValue();
99720         } else {
99721             me.collapse();
99722         }
99723     },
99724
99725     // private
99726     assertValue: function() {
99727         var me = this,
99728             value = me.getRawValue(),
99729             rec;
99730
99731         if (me.multiSelect) {
99732             // For multiselect, check that the current displayed value matches the current
99733             // selection, if it does not then revert to the most recent selection.
99734             if (value !== me.getDisplayValue()) {
99735                 me.setValue(me.lastSelection);
99736             }
99737         } else {
99738             // For single-select, match the displayed value to a record and select it,
99739             // if it does not match a record then revert to the most recent selection.
99740             rec = me.findRecordByDisplay(value);
99741             if (rec) {
99742                 me.select(rec);
99743             } else {
99744                 me.setValue(me.lastSelection);
99745             }
99746         }
99747         me.collapse();
99748     },
99749
99750     onTypeAhead: function() {
99751         var me = this,
99752             displayField = me.displayField,
99753             record = me.store.findRecord(displayField, me.getRawValue()),
99754             boundList = me.getPicker(),
99755             newValue, len, selStart;
99756
99757         if (record) {
99758             newValue = record.get(displayField);
99759             len = newValue.length;
99760             selStart = me.getRawValue().length;
99761
99762             boundList.highlightItem(boundList.getNode(record));
99763
99764             if (selStart !== 0 && selStart !== len) {
99765                 me.setRawValue(newValue);
99766                 me.selectText(selStart, newValue.length);
99767             }
99768         }
99769     },
99770
99771     // invoked when a different store is bound to this combo
99772     // than the original
99773     resetToDefault: function() {
99774
99775     },
99776
99777     bindStore: function(store, initial) {
99778         var me = this,
99779             oldStore = me.store;
99780
99781         // this code directly accesses this.picker, bc invoking getPicker
99782         // would create it when we may be preping to destroy it
99783         if (oldStore && !initial) {
99784             if (oldStore !== store && oldStore.autoDestroy) {
99785                 oldStore.destroy();
99786             } else {
99787                 oldStore.un({
99788                     scope: me,
99789                     load: me.onLoad,
99790                     exception: me.collapse
99791                 });
99792             }
99793             if (!store) {
99794                 me.store = null;
99795                 if (me.picker) {
99796                     me.picker.bindStore(null);
99797                 }
99798             }
99799         }
99800         if (store) {
99801             if (!initial) {
99802                 me.resetToDefault();
99803             }
99804
99805             me.store = Ext.data.StoreManager.lookup(store);
99806             me.store.on({
99807                 scope: me,
99808                 load: me.onLoad,
99809                 exception: me.collapse
99810             });
99811
99812             if (me.picker) {
99813                 me.picker.bindStore(store);
99814             }
99815         }
99816     },
99817
99818     onLoad: function() {
99819         var me = this,
99820             value = me.value;
99821
99822         me.syncSelection();
99823         if (me.picker && !me.picker.getSelectionModel().hasSelection()) {
99824             me.doAutoSelect();
99825         }
99826     },
99827
99828     /**
99829      * @private
99830      * Execute the query with the raw contents within the textfield.
99831      */
99832     doRawQuery: function() {
99833         this.doQuery(this.getRawValue());
99834     },
99835
99836     /**
99837      * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the
99838      * query allowing the query action to be canceled if needed.
99839      * @param {String} queryString The SQL query to execute
99840      * @param {Boolean} forceAll <tt>true</tt> to force the query to execute even if there are currently fewer
99841      * characters in the field than the minimum specified by the <tt>{@link #minChars}</tt> config option.  It
99842      * also clears any filter previously saved in the current store (defaults to <tt>false</tt>)
99843      * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery} handler.
99844      */
99845     doQuery: function(queryString, forceAll) {
99846         queryString = queryString || '';
99847
99848         // store in object and pass by reference in 'beforequery'
99849         // so that client code can modify values.
99850         var me = this,
99851             qe = {
99852                 query: queryString,
99853                 forceAll: forceAll,
99854                 combo: me,
99855                 cancel: false
99856             },
99857             store = me.store,
99858             isLocalMode = me.queryMode === 'local';
99859
99860         if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
99861             return false;
99862         }
99863
99864         // get back out possibly modified values
99865         queryString = qe.query;
99866         forceAll = qe.forceAll;
99867
99868         // query permitted to run
99869         if (forceAll || (queryString.length >= me.minChars)) {
99870             // expand before starting query so LoadMask can position itself correctly
99871             me.expand();
99872
99873             // make sure they aren't querying the same thing
99874             if (!me.queryCaching || me.lastQuery !== queryString) {
99875                 me.lastQuery = queryString;
99876                 store.clearFilter(!forceAll);
99877                 if (isLocalMode) {
99878                     if (!forceAll) {
99879                         store.filter(me.displayField, queryString);
99880                     }
99881                 } else {
99882                     store.load({
99883                         params: me.getParams(queryString)
99884                     });
99885                 }
99886             }
99887
99888             // Clear current selection if it does not match the current value in the field
99889             if (me.getRawValue() !== me.getDisplayValue()) {
99890                 me.ignoreSelection++;
99891                 me.picker.getSelectionModel().deselectAll();
99892                 me.ignoreSelection--;
99893             }
99894
99895             if (isLocalMode) {
99896                 me.doAutoSelect();
99897             }
99898             if (me.typeAhead) {
99899                 me.doTypeAhead();
99900             }
99901         }
99902         return true;
99903     },
99904
99905     // private
99906     getParams: function(queryString) {
99907         var p = {},
99908             pageSize = this.pageSize,
99909             param = this.queryParam;
99910             
99911         if (param) {
99912             p[param] = queryString;
99913         }
99914         
99915         if (pageSize) {
99916             p.start = 0;
99917             p.limit = pageSize;
99918         }
99919         return p;
99920     },
99921
99922     /**
99923      * @private
99924      * If the autoSelect config is true, and the picker is open, highlights the first item.
99925      */
99926     doAutoSelect: function() {
99927         var me = this,
99928             picker = me.picker,
99929             lastSelected, itemNode;
99930         if (picker && me.autoSelect && me.store.getCount() > 0) {
99931             // Highlight the last selected item and scroll it into view
99932             lastSelected = picker.getSelectionModel().lastSelected;
99933             itemNode = picker.getNode(lastSelected || 0);
99934             if (itemNode) {
99935                 picker.highlightItem(itemNode);
99936                 picker.listEl.scrollChildIntoView(itemNode, false);
99937             }
99938         }
99939     },
99940
99941     doTypeAhead: function() {
99942         if (!this.typeAheadTask) {
99943             this.typeAheadTask = Ext.create('Ext.util.DelayedTask', this.onTypeAhead, this);
99944         }
99945         if (this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE) {
99946             this.typeAheadTask.delay(this.typeAheadDelay);
99947         }
99948     },
99949
99950     onTriggerClick: function() {
99951         var me = this;
99952         if (!me.readOnly && !me.disabled) {
99953             if (me.isExpanded) {
99954                 me.collapse();
99955             } else {
99956                 me.onFocus({});
99957                 if (me.triggerAction === 'all') {
99958                     me.doQuery(me.allQuery, true);
99959                 } else {
99960                     me.doQuery(me.getRawValue());
99961                 }
99962             }
99963             me.inputEl.focus();
99964         }
99965     },
99966
99967
99968     // store the last key and doQuery if relevant
99969     onKeyUp: function(e, t) {
99970         var me = this,
99971             key = e.getKey();
99972
99973         if (!me.readOnly && !me.disabled && me.editable) {
99974             me.lastKey = key;
99975             // we put this in a task so that we can cancel it if a user is
99976             // in and out before the queryDelay elapses
99977
99978             // perform query w/ any normal key or backspace or delete
99979             if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
99980                 me.doQueryTask.delay(me.queryDelay);
99981             }
99982         }
99983         
99984         if (me.enableKeyEvents) {
99985             me.callParent(arguments);
99986         }
99987     },
99988
99989     initEvents: function() {
99990         var me = this;
99991         me.callParent();
99992
99993         /*
99994          * Setup keyboard handling. If enableKeyEvents is true, we already have 
99995          * a listener on the inputEl for keyup, so don't create a second.
99996          */
99997         if (!me.enableKeyEvents) {
99998             me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
99999         }
100000     },
100001
100002     createPicker: function() {
100003         var me = this,
100004             picker,
100005             menuCls = Ext.baseCSSPrefix + 'menu',
100006             opts = Ext.apply({
100007                 selModel: {
100008                     mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
100009                 },
100010                 floating: true,
100011                 hidden: true,
100012                 ownerCt: me.ownerCt,
100013                 cls: me.el.up('.' + menuCls) ? menuCls : '',
100014                 store: me.store,
100015                 displayField: me.displayField,
100016                 focusOnToFront: false,
100017                 pageSize: me.pageSize
100018             }, me.listConfig, me.defaultListConfig);
100019
100020         picker = me.picker = Ext.create('Ext.view.BoundList', opts);
100021
100022         me.mon(picker, {
100023             itemclick: me.onItemClick,
100024             refresh: me.onListRefresh,
100025             scope: me
100026         });
100027
100028         me.mon(picker.getSelectionModel(), {
100029             selectionChange: me.onListSelectionChange,
100030             scope: me
100031         });
100032
100033         return picker;
100034     },
100035
100036     onListRefresh: function() {
100037         this.alignPicker();
100038         this.syncSelection();
100039     },
100040     
100041     onItemClick: function(picker, record){
100042         /*
100043          * If we're doing single selection, the selection change events won't fire when
100044          * clicking on the selected element. Detect it here.
100045          */
100046         var me = this,
100047             lastSelection = me.lastSelection,
100048             valueField = me.valueField,
100049             selected;
100050         
100051         if (!me.multiSelect && lastSelection) {
100052             selected = lastSelection[0];
100053             if (record.get(valueField) === selected.get(valueField)) {
100054                 me.collapse();
100055             }
100056         }   
100057     },
100058
100059     onListSelectionChange: function(list, selectedRecords) {
100060         var me = this;
100061         // Only react to selection if it is not called from setValue, and if our list is
100062         // expanded (ignores changes to the selection model triggered elsewhere)
100063         if (!me.ignoreSelection && me.isExpanded) {
100064             if (!me.multiSelect) {
100065                 Ext.defer(me.collapse, 1, me);
100066             }
100067             me.setValue(selectedRecords, false);
100068             if (selectedRecords.length > 0) {
100069                 me.fireEvent('select', me, selectedRecords);
100070             }
100071             me.inputEl.focus();
100072         }
100073     },
100074
100075     /**
100076      * @private
100077      * Enables the key nav for the BoundList when it is expanded.
100078      */
100079     onExpand: function() {
100080         var me = this,
100081             keyNav = me.listKeyNav,
100082             selectOnTab = me.selectOnTab,
100083             picker = me.getPicker();
100084
100085         // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
100086         if (keyNav) {
100087             keyNav.enable();
100088         } else {
100089             keyNav = me.listKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
100090                 boundList: picker,
100091                 forceKeyDown: true,
100092                 tab: function(e) {
100093                     if (selectOnTab) {
100094                         this.selectHighlighted(e);
100095                         me.triggerBlur();
100096                     }
100097                     // Tab key event is allowed to propagate to field
100098                     return true;
100099                 }
100100             });
100101         }
100102
100103         // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
100104         if (selectOnTab) {
100105             me.ignoreMonitorTab = true;
100106         }
100107
100108         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
100109         me.inputEl.focus();
100110     },
100111
100112     /**
100113      * @private
100114      * Disables the key nav for the BoundList when it is collapsed.
100115      */
100116     onCollapse: function() {
100117         var me = this,
100118             keyNav = me.listKeyNav;
100119         if (keyNav) {
100120             keyNav.disable();
100121             me.ignoreMonitorTab = false;
100122         }
100123     },
100124
100125     /**
100126      * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
100127      * @param r
100128      */
100129     select: function(r) {
100130         this.setValue(r, true);
100131     },
100132
100133     /**
100134      * Find the record by searching for a specific field/value combination
100135      * Returns an Ext.data.Record or false
100136      * @private
100137      */
100138     findRecord: function(field, value) {
100139         var ds = this.store,
100140             idx = ds.findExact(field, value);
100141         return idx !== -1 ? ds.getAt(idx) : false;
100142     },
100143     findRecordByValue: function(value) {
100144         return this.findRecord(this.valueField, value);
100145     },
100146     findRecordByDisplay: function(value) {
100147         return this.findRecord(this.displayField, value);
100148     },
100149
100150     /**
100151      * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that
100152      * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the
100153      * field.  If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be
100154      * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.
100155      * @param {String|Array} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
100156      * or an Array of Strings or Models.
100157      * @return {Ext.form.field.Field} this
100158      */
100159     setValue: function(value, doSelect) {
100160         var me = this,
100161             valueNotFoundText = me.valueNotFoundText,
100162             inputEl = me.inputEl,
100163             i, len, record,
100164             models = [],
100165             displayTplData = [],
100166             processedValue = [];
100167
100168         if (me.store.loading) {
100169             // Called while the Store is loading. Ensure it is processed by the onLoad method.
100170             me.value = value;
100171             return me;
100172         }
100173
100174         // This method processes multi-values, so ensure value is an array.
100175         value = Ext.Array.from(value);
100176
100177         // Loop through values
100178         for (i = 0, len = value.length; i < len; i++) {
100179             record = value[i];
100180             if (!record || !record.isModel) {
100181                 record = me.findRecordByValue(record);
100182             }
100183             // record found, select it.
100184             if (record) {
100185                 models.push(record);
100186                 displayTplData.push(record.data);
100187                 processedValue.push(record.get(me.valueField));
100188             }
100189             // record was not found, this could happen because
100190             // store is not loaded or they set a value not in the store
100191             else {
100192                 // if valueNotFoundText is defined, display it, otherwise display nothing for this value
100193                 if (Ext.isDefined(valueNotFoundText)) {
100194                     displayTplData.push(valueNotFoundText);
100195                 }
100196                 processedValue.push(value[i]);
100197             }
100198         }
100199
100200         // Set the value of this field. If we are multiselecting, then that is an array.
100201         me.value = me.multiSelect ? processedValue : processedValue[0];
100202         if (!Ext.isDefined(me.value)) {
100203             me.value = null;
100204         }
100205         me.displayTplData = displayTplData; //store for getDisplayValue method
100206         me.lastSelection = me.valueModels = models;
100207
100208         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
100209             inputEl.removeCls(me.emptyCls);
100210         }
100211
100212         // Calculate raw value from the collection of Model data
100213         me.setRawValue(me.getDisplayValue());
100214         me.checkChange();
100215
100216         if (doSelect !== false) {
100217             me.syncSelection();
100218         }
100219         me.applyEmptyText();
100220
100221         return me;
100222     },
100223
100224     /**
100225      * @private Generate the string value to be displayed in the text field for the currently stored value
100226      */
100227     getDisplayValue: function() {
100228         return this.displayTpl.apply(this.displayTplData);
100229     },
100230
100231     getValue: function() {
100232         // If the user has not changed the raw field value since a value was selected from the list,
100233         // then return the structured value from the selection. If the raw field value is different
100234         // than what would be displayed due to selection, return that raw value.
100235         var me = this,
100236             picker = me.picker,
100237             rawValue = me.getRawValue(), //current value of text field
100238             value = me.value; //stored value from last selection or setValue() call
100239
100240         if (me.getDisplayValue() !== rawValue) {
100241             value = rawValue;
100242             me.value = me.displayTplData = me.valueModels = null;
100243             if (picker) {
100244                 me.ignoreSelection++;
100245                 picker.getSelectionModel().deselectAll();
100246                 me.ignoreSelection--;
100247             }
100248         }
100249
100250         return value;
100251     },
100252
100253     getSubmitValue: function() {
100254         return this.getValue();
100255     },
100256
100257     isEqual: function(v1, v2) {
100258         var fromArray = Ext.Array.from,
100259             i, len;
100260
100261         v1 = fromArray(v1);
100262         v2 = fromArray(v2);
100263         len = v1.length;
100264
100265         if (len !== v2.length) {
100266             return false;
100267         }
100268
100269         for(i = 0; i < len; i++) {
100270             if (v2[i] !== v1[i]) {
100271                 return false;
100272             }
100273         }
100274
100275         return true;
100276     },
100277
100278     /**
100279      * Clears any value currently set in the ComboBox.
100280      */
100281     clearValue: function() {
100282         this.setValue([]);
100283     },
100284
100285     /**
100286      * @private Synchronizes the selection in the picker to match the current value of the combobox.
100287      */
100288     syncSelection: function() {
100289         var me = this,
100290             ExtArray = Ext.Array,
100291             picker = me.picker,
100292             selection, selModel;
100293         if (picker) {
100294             // From the value, find the Models that are in the store's current data
100295             selection = [];
100296             ExtArray.forEach(me.valueModels || [], function(value) {
100297                 if (value && value.isModel && me.store.indexOf(value) >= 0) {
100298                     selection.push(value);
100299                 }
100300             });
100301
100302             // Update the selection to match
100303             me.ignoreSelection++;
100304             selModel = picker.getSelectionModel();
100305             selModel.deselectAll();
100306             if (selection.length) {
100307                 selModel.select(selection);
100308             }
100309             me.ignoreSelection--;
100310         }
100311     }
100312 });
100313
100314 /**
100315  * @private
100316  * @class Ext.picker.Month
100317  * @extends Ext.Component
100318  * <p>A month picker component. This class is used by the {@link Ext.picker.Date DatePicker} class
100319  * to allow browsing and selection of year/months combinations.</p>
100320  * @constructor
100321  * Create a new MonthPicker
100322  * @param {Object} config The config object
100323  * @xtype monthpicker
100324  * @private
100325  */
100326 Ext.define('Ext.picker.Month', {
100327     extend: 'Ext.Component',
100328     requires: ['Ext.XTemplate', 'Ext.util.ClickRepeater', 'Ext.Date', 'Ext.button.Button'],
100329     alias: 'widget.monthpicker',
100330     alternateClassName: 'Ext.MonthPicker',
100331
100332     renderTpl: [
100333         '<div class="{baseCls}-body">',
100334           '<div class="{baseCls}-months">',
100335               '<tpl for="months">',
100336                   '<div class="{parent.baseCls}-item {parent.baseCls}-month"><a href="#" hidefocus="on">{.}</a></div>',
100337               '</tpl>',
100338           '</div>',
100339           '<div class="{baseCls}-years">',
100340               '<div class="{baseCls}-yearnav">',
100341                   '<button class="{baseCls}-yearnav-prev"></button>',
100342                   '<button class="{baseCls}-yearnav-next"></button>',
100343               '</div>',
100344               '<tpl for="years">',
100345                   '<div class="{parent.baseCls}-item {parent.baseCls}-year"><a href="#" hidefocus="on">{.}</a></div>',
100346               '</tpl>',
100347           '</div>',
100348         '</div>',
100349         '<div class="' + Ext.baseCSSPrefix + 'clear"></div>',
100350         '<tpl if="showButtons">',
100351           '<div class="{baseCls}-buttons"></div>',
100352         '</tpl>'
100353     ],
100354
100355     /**
100356      * @cfg {String} okText The text to display on the ok button. Defaults to <tt>'OK'</tt>
100357      */
100358     okText: 'OK',
100359
100360     /**
100361      * @cfg {String} cancelText The text to display on the cancel button. Defaults to <tt>'Cancel'</tt>
100362      */
100363     cancelText: 'Cancel',
100364
100365     /**
100366      * @cfg {String} baseCls The base CSS class to apply to the picker element. Defaults to <tt>'x-monthpicker'</tt>
100367      */
100368     baseCls: Ext.baseCSSPrefix + 'monthpicker',
100369
100370     /**
100371      * @cfg {Boolean} showButtons True to show ok and cancel buttons below the picker. Defaults to <tt>true</tt>.
100372      */
100373     showButtons: true,
100374
100375     /**
100376      * @cfg {String} selectedCls The class to be added to selected items in the picker. Defaults to
100377      * <tt>'x-monthpicker-selected'</tt>
100378      */
100379
100380     /**
100381      * @cfg {Date/Array} value The default value to set. See {#setValue setValue}
100382      */
100383
100384     width: 175,
100385
100386     height: 195,
100387
100388
100389     // private
100390     totalYears: 10,
100391     yearOffset: 5, // 10 years in total, 2 per row
100392     monthOffset: 6, // 12 months, 2 per row
100393
100394     // private, inherit docs
100395     initComponent: function(){
100396         var me = this;
100397
100398         me.selectedCls = me.baseCls + '-selected';
100399         me.addEvents(
100400             /**
100401              * @event cancelclick
100402              * Fires when the cancel button is pressed.
100403              * @param {Ext.picker.Month} this
100404              */
100405             'cancelclick',
100406
100407             /**
100408              * @event monthclick
100409              * Fires when a month is clicked.
100410              * @param {Ext.picker.Month} this
100411              * @param {Array} value The current value
100412              */
100413             'monthclick',
100414
100415             /**
100416              * @event monthdblclick
100417              * Fires when a month is clicked.
100418              * @param {Ext.picker.Month} this
100419              * @param {Array} value The current value
100420              */
100421             'monthdblclick',
100422
100423             /**
100424              * @event okclick
100425              * Fires when the ok button is pressed.
100426              * @param {Ext.picker.Month} this
100427              * @param {Array} value The current value
100428              */
100429             'okclick',
100430
100431             /**
100432              * @event select
100433              * Fires when a month/year is selected.
100434              * @param {Ext.picker.Month} this
100435              * @param {Array} value The current value
100436              */
100437             'select',
100438
100439             /**
100440              * @event yearclick
100441              * Fires when a year is clicked.
100442              * @param {Ext.picker.Month} this
100443              * @param {Array} value The current value
100444              */
100445             'yearclick',
100446
100447             /**
100448              * @event yeardblclick
100449              * Fires when a year is clicked.
100450              * @param {Ext.picker.Month} this
100451              * @param {Array} value The current value
100452              */
100453             'yeardblclick'
100454         );
100455
100456         me.setValue(me.value);
100457         me.activeYear = me.getYear(new Date().getFullYear() - 4, -4);
100458         this.callParent();
100459     },
100460
100461     // private, inherit docs
100462     onRender: function(ct, position){
100463         var me = this,
100464             i = 0,
100465             months = [],
100466             shortName = Ext.Date.getShortMonthName,
100467             monthLen = me.monthOffset;
100468
100469         for (; i < monthLen; ++i) {
100470             months.push(shortName(i), shortName(i + monthLen));
100471         }
100472
100473         Ext.apply(me.renderData, {
100474             months: months,
100475             years: me.getYears(),
100476             showButtons: me.showButtons
100477         });
100478
100479         Ext.apply(me.renderSelectors, {
100480             bodyEl: '.' + me.baseCls + '-body',
100481             prevEl: '.' + me.baseCls + '-yearnav-prev',
100482             nextEl: '.' + me.baseCls + '-yearnav-next',
100483             buttonsEl: '.' + me.baseCls + '-buttons'
100484         });
100485         this.callParent([ct, position]);
100486     },
100487
100488     // private, inherit docs
100489     afterRender: function(){
100490         var me = this,
100491             body = me.bodyEl,
100492             buttonsEl = me.buttonsEl;
100493
100494         me.callParent();
100495
100496         me.mon(body, 'click', me.onBodyClick, me);
100497         me.mon(body, 'dblclick', me.onBodyClick, me);
100498
100499         // keep a reference to the year/month elements since we'll be re-using them
100500         me.years = body.select('.' + me.baseCls + '-year a');
100501         me.months = body.select('.' + me.baseCls + '-month a');
100502
100503         if (me.showButtons) {
100504             me.okBtn = Ext.create('Ext.button.Button', {
100505                 text: me.okText,
100506                 renderTo: buttonsEl,
100507                 handler: me.onOkClick,
100508                 scope: me
100509             });
100510             me.cancelBtn = Ext.create('Ext.button.Button', {
100511                 text: me.cancelText,
100512                 renderTo: buttonsEl,
100513                 handler: me.onCancelClick,
100514                 scope: me
100515             });
100516         }
100517
100518         me.backRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
100519             handler: Ext.Function.bind(me.adjustYear, me, [-me.totalYears])
100520         });
100521
100522         me.prevEl.addClsOnOver(me.baseCls + '-yearnav-prev-over');
100523         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
100524             handler: Ext.Function.bind(me.adjustYear, me, [me.totalYears])
100525         });
100526         me.nextEl.addClsOnOver(me.baseCls + '-yearnav-next-over');
100527         me.updateBody();
100528     },
100529
100530     /**
100531      * Set the value for the picker.
100532      * @param {Date/Array} value The value to set. It can be a Date object, where the month/year will be extracted, or
100533      * it can be an array, with the month as the first index and the year as the second.
100534      * @return {Ext.picker.Month} this
100535      */
100536     setValue: function(value){
100537         var me = this,
100538             active = me.activeYear,
100539             offset = me.monthOffset,
100540             year,
100541             index;
100542
100543         if (!value) {
100544             me.value = [null, null];
100545         } else if (Ext.isDate(value)) {
100546             me.value = [value.getMonth(), value.getFullYear()];
100547         } else {
100548             me.value = [value[0], value[1]];
100549         }
100550
100551         if (me.rendered) {
100552             year = me.value[1];
100553             if (year !== null) {
100554                 if ((year < active || year > active + me.yearOffset)) {
100555                     me.activeYear = year - me.yearOffset + 1;
100556                 }
100557             }
100558             me.updateBody();
100559         }
100560
100561         return me;
100562     },
100563
100564     /**
100565      * Gets the selected value. It is returned as an array [month, year]. It may
100566      * be a partial value, for example [null, 2010]. The month is returned as
100567      * 0 based.
100568      * @return {Array} The selected value
100569      */
100570     getValue: function(){
100571         return this.value;
100572     },
100573
100574     /**
100575      * Checks whether the picker has a selection
100576      * @return {Boolean} Returns true if both a month and year have been selected
100577      */
100578     hasSelection: function(){
100579         var value = this.value;
100580         return value[0] !== null && value[1] !== null;
100581     },
100582
100583     /**
100584      * Get an array of years to be pushed in the template. It is not in strict
100585      * numerical order because we want to show them in columns.
100586      * @private
100587      * @return {Array} An array of years
100588      */
100589     getYears: function(){
100590         var me = this,
100591             offset = me.yearOffset,
100592             start = me.activeYear, // put the "active" year on the left
100593             end = start + offset,
100594             i = start,
100595             years = [];
100596
100597         for (; i < end; ++i) {
100598             years.push(i, i + offset);
100599         }
100600
100601         return years;
100602     },
100603
100604     /**
100605      * Update the years in the body based on any change
100606      * @private
100607      */
100608     updateBody: function(){
100609         var me = this,
100610             years = me.years,
100611             months = me.months,
100612             yearNumbers = me.getYears(),
100613             cls = me.selectedCls,
100614             value = me.getYear(null),
100615             month = me.value[0],
100616             monthOffset = me.monthOffset,
100617             year;
100618
100619         if (me.rendered) {
100620             years.removeCls(cls);
100621             months.removeCls(cls);
100622             years.each(function(el, all, index){
100623                 year = yearNumbers[index];
100624                 el.dom.innerHTML = year;
100625                 if (year == value) {
100626                     el.dom.className = cls;
100627                 }
100628             });
100629             if (month !== null) {
100630                 if (month < monthOffset) {
100631                     month = month * 2;
100632                 } else {
100633                     month = (month - monthOffset) * 2 + 1;
100634                 }
100635                 months.item(month).addCls(cls);
100636             }
100637         }
100638     },
100639
100640     /**
100641      * Gets the current year value, or the default.
100642      * @private
100643      * @param {Number} defaultValue The default value to use if the year is not defined.
100644      * @param {Number} offset A number to offset the value by
100645      * @return {Number} The year value
100646      */
100647     getYear: function(defaultValue, offset) {
100648         var year = this.value[1];
100649         offset = offset || 0;
100650         return year === null ? defaultValue : year + offset;
100651     },
100652
100653     /**
100654      * React to clicks on the body
100655      * @private
100656      */
100657     onBodyClick: function(e, t) {
100658         var me = this,
100659             isDouble = e.type == 'dblclick';
100660
100661         if (e.getTarget('.' + me.baseCls + '-month')) {
100662             e.stopEvent();
100663             me.onMonthClick(t, isDouble);
100664         } else if (e.getTarget('.' + me.baseCls + '-year')) {
100665             e.stopEvent();
100666             me.onYearClick(t, isDouble);
100667         }
100668     },
100669
100670     /**
100671      * Modify the year display by passing an offset.
100672      * @param {Number} offset The offset to move by. If not specified, it defaults to 10.
100673      */
100674     adjustYear: function(offset){
100675         if (typeof offset != 'number') {
100676             offset = this.totalYears;
100677         }
100678         this.activeYear += offset;
100679         this.updateBody();
100680     },
100681
100682     /**
100683      * React to the ok button being pressed
100684      * @private
100685      */
100686     onOkClick: function(){
100687         this.fireEvent('okclick', this, this.value);
100688     },
100689
100690     /**
100691      * React to the cancel button being pressed
100692      * @private
100693      */
100694     onCancelClick: function(){
100695         this.fireEvent('cancelclick', this);
100696     },
100697
100698     /**
100699      * React to a month being clicked
100700      * @private
100701      * @param {HTMLElement} target The element that was clicked
100702      * @param {Boolean} isDouble True if the event was a doubleclick
100703      */
100704     onMonthClick: function(target, isDouble){
100705         var me = this;
100706         me.value[0] = me.resolveOffset(me.months.indexOf(target), me.monthOffset);
100707         me.updateBody();
100708         me.fireEvent('month' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
100709         me.fireEvent('select', me, me.value);
100710     },
100711
100712     /**
100713      * React to a year being clicked
100714      * @private
100715      * @param {HTMLElement} target The element that was clicked
100716      * @param {Boolean} isDouble True if the event was a doubleclick
100717      */
100718     onYearClick: function(target, isDouble){
100719         var me = this;
100720         me.value[1] = me.activeYear + me.resolveOffset(me.years.indexOf(target), me.yearOffset);
100721         me.updateBody();
100722         me.fireEvent('year' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
100723         me.fireEvent('select', me, me.value);
100724
100725     },
100726
100727     /**
100728      * Returns an offsetted number based on the position in the collection. Since our collections aren't
100729      * numerically ordered, this function helps to normalize those differences.
100730      * @private
100731      * @param {Object} index
100732      * @param {Object} offset
100733      * @return {Number} The correctly offsetted number
100734      */
100735     resolveOffset: function(index, offset){
100736         if (index % 2 === 0) {
100737             return (index / 2);
100738         } else {
100739             return offset + Math.floor(index / 2);
100740         }
100741     },
100742
100743     // private, inherit docs
100744     beforeDestroy: function(){
100745         var me = this;
100746         me.years = me.months = null;
100747         Ext.destroyMembers('backRepeater', 'nextRepeater', 'okBtn', 'cancelBtn');
100748         this.callParent();
100749     }
100750 });
100751
100752 /**
100753  * @class Ext.picker.Date
100754  * @extends Ext.Component
100755  * <p>A date picker. This class is used by the {@link Ext.form.field.Date} field to allow browsing and
100756  * selection of valid dates in a popup next to the field, but may also be used with other components.</p>
100757  * <p>Typically you will need to implement a handler function to be notified when the user chooses a color from the
100758  * picker; you can register the handler using the {@link #select} event, or by implementing the {@link #handler}
100759  * method.</p>
100760  * <p>By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate},
100761  * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.</p>
100762  * <p>All the string values documented below may be overridden by including an Ext locale file in your page.</p>
100763  * <p>Example usage:</p>
100764  * <pre><code>new Ext.panel.Panel({
100765     title: 'Choose a future date:',
100766     width: 200,
100767     bodyPadding: 10,
100768     renderTo: Ext.getBody(),
100769     items: [{
100770         xtype: 'datepicker',
100771         minDate: new Date(),
100772         handler: function(picker, date) {
100773             // do something with the selected date
100774         }
100775     }]
100776 });</code></pre>
100777  * {@img Ext.picker.Date/Ext.picker.Date.png Ext.picker.Date component}
100778  *
100779  * @constructor
100780  * Create a new DatePicker
100781  * @param {Object} config The config object
100782  *
100783  * @xtype datepicker
100784  */
100785 Ext.define('Ext.picker.Date', {
100786     extend: 'Ext.Component',
100787     requires: [
100788         'Ext.XTemplate',
100789         'Ext.button.Button',
100790         'Ext.button.Split',
100791         'Ext.util.ClickRepeater',
100792         'Ext.util.KeyNav',
100793         'Ext.EventObject',
100794         'Ext.fx.Manager',
100795         'Ext.picker.Month'
100796     ],
100797     alias: 'widget.datepicker',
100798     alternateClassName: 'Ext.DatePicker',
100799
100800     renderTpl: [
100801         '<div class="{cls}" id="{id}" role="grid" title="{ariaTitle} {value:this.longDay}">',
100802             '<div role="presentation" class="{baseCls}-header">',
100803                 '<div class="{baseCls}-prev"><a href="#" role="button" title="{prevText}"></a></div>',
100804                 '<div class="{baseCls}-month"></div>',
100805                 '<div class="{baseCls}-next"><a href="#" role="button" title="{nextText}"></a></div>',
100806             '</div>',
100807             '<table class="{baseCls}-inner" cellspacing="0" role="presentation">',
100808                 '<thead role="presentation"><tr role="presentation">',
100809                     '<tpl for="dayNames">',
100810                         '<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>',
100811                     '</tpl>',
100812                 '</tr></thead>',
100813                 '<tbody role="presentation"><tr role="presentation">',
100814                     '<tpl for="days">',
100815                         '{#:this.isEndOfWeek}',
100816                         '<td role="gridcell" id="{[Ext.id()]}">',
100817                             '<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">',
100818                                 '<em role="presentation"><span role="presentation"></span></em>',
100819                             '</a>',
100820                         '</td>',
100821                     '</tpl>',
100822                 '</tr></tbody>',
100823             '</table>',
100824             '<tpl if="showToday">',
100825                 '<div role="presentation" class="{baseCls}-footer"></div>',
100826             '</tpl>',
100827         '</div>',
100828         {
100829             firstInitial: function(value) {
100830                 return value.substr(0,1);
100831             },
100832             isEndOfWeek: function(value) {
100833                 // convert from 1 based index to 0 based
100834                 // by decrementing value once.
100835                 value--;
100836                 var end = value % 7 === 0 && value !== 0;
100837                 return end ? '</tr><tr role="row">' : '';
100838             },
100839             longDay: function(value){
100840                 return Ext.Date.format(value, this.longDayFormat);
100841             }
100842         }
100843     ],
100844
100845     ariaTitle: 'Date Picker',
100846     /**
100847      * @cfg {String} todayText
100848      * The text to display on the button that selects the current date (defaults to <code>'Today'</code>)
100849      */
100850     todayText : 'Today',
100851     /**
100852      * @cfg {Function} handler
100853      * Optional. A function that will handle the select event of this picker.
100854      * The handler is passed the following parameters:<div class="mdetail-params"><ul>
100855      * <li><code>picker</code> : Ext.picker.Date <div class="sub-desc">This Date picker.</div></li>
100856      * <li><code>date</code> : Date <div class="sub-desc">The selected date.</div></li>
100857      * </ul></div>
100858      */
100859     /**
100860      * @cfg {Object} scope
100861      * The scope (<code><b>this</b></code> reference) in which the <code>{@link #handler}</code>
100862      * function will be called.  Defaults to this DatePicker instance.
100863      */
100864     /**
100865      * @cfg {String} todayTip
100866      * A string used to format the message for displaying in a tooltip over the button that
100867      * selects the current date. Defaults to <code>'{0} (Spacebar)'</code> where
100868      * the <code>{0}</code> token is replaced by today's date.
100869      */
100870     todayTip : '{0} (Spacebar)',
100871     /**
100872      * @cfg {String} minText
100873      * The error text to display if the minDate validation fails (defaults to <code>'This date is before the minimum date'</code>)
100874      */
100875     minText : 'This date is before the minimum date',
100876     /**
100877      * @cfg {String} maxText
100878      * The error text to display if the maxDate validation fails (defaults to <code>'This date is after the maximum date'</code>)
100879      */
100880     maxText : 'This date is after the maximum date',
100881     /**
100882      * @cfg {String} format
100883      * The default date format string which can be overriden for localization support.  The format must be
100884      * valid according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
100885      */
100886     /**
100887      * @cfg {String} disabledDaysText
100888      * The tooltip to display when the date falls on a disabled day (defaults to <code>'Disabled'</code>)
100889      */
100890     disabledDaysText : 'Disabled',
100891     /**
100892      * @cfg {String} disabledDatesText
100893      * The tooltip text to display when the date falls on a disabled date (defaults to <code>'Disabled'</code>)
100894      */
100895     disabledDatesText : 'Disabled',
100896     /**
100897      * @cfg {Array} monthNames
100898      * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
100899      */
100900     /**
100901      * @cfg {Array} dayNames
100902      * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
100903      */
100904     /**
100905      * @cfg {String} nextText
100906      * The next month navigation button tooltip (defaults to <code>'Next Month (Control+Right)'</code>)
100907      */
100908     nextText : 'Next Month (Control+Right)',
100909     /**
100910      * @cfg {String} prevText
100911      * The previous month navigation button tooltip (defaults to <code>'Previous Month (Control+Left)'</code>)
100912      */
100913     prevText : 'Previous Month (Control+Left)',
100914     /**
100915      * @cfg {String} monthYearText
100916      * The header month selector tooltip (defaults to <code>'Choose a month (Control+Up/Down to move years)'</code>)
100917      */
100918     monthYearText : 'Choose a month (Control+Up/Down to move years)',
100919     /**
100920      * @cfg {Number} startDay
100921      * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
100922      */
100923     startDay : 0,
100924     /**
100925      * @cfg {Boolean} showToday
100926      * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar
100927      * that selects the current date (defaults to <code>true</code>).
100928      */
100929     showToday : true,
100930     /**
100931      * @cfg {Date} minDate
100932      * Minimum allowable date (JavaScript date object, defaults to null)
100933      */
100934     /**
100935      * @cfg {Date} maxDate
100936      * Maximum allowable date (JavaScript date object, defaults to null)
100937      */
100938     /**
100939      * @cfg {Array} disabledDays
100940      * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
100941      */
100942     /**
100943      * @cfg {RegExp} disabledDatesRE
100944      * JavaScript regular expression used to disable a pattern of dates (defaults to null).  The {@link #disabledDates}
100945      * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
100946      * disabledDates value.
100947      */
100948     /**
100949      * @cfg {Array} disabledDates
100950      * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular
100951      * expression so they are very powerful. Some examples:
100952      * <ul>
100953      * <li>['03/08/2003', '09/16/2003'] would disable those exact dates</li>
100954      * <li>['03/08', '09/16'] would disable those days for every year</li>
100955      * <li>['^03/08'] would only match the beginning (useful if you are using short years)</li>
100956      * <li>['03/../2006'] would disable every day in March 2006</li>
100957      * <li>['^03'] would disable every day in every March</li>
100958      * </ul>
100959      * Note that the format of the dates included in the array should exactly match the {@link #format} config.
100960      * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to
100961      * escape the dot when restricting dates. For example: ['03\\.08\\.03'].
100962      */
100963
100964     /**
100965      * @cfg {Boolean} disableAnim True to disable animations when showing the month picker. Defaults to <tt>false</tt>.
100966      */
100967     disableAnim: true,
100968
100969     /**
100970      * @cfg {String} baseCls
100971      * The base CSS class to apply to this components element (defaults to <tt>'x-datepicker'</tt>).
100972      */
100973     baseCls: Ext.baseCSSPrefix + 'datepicker',
100974
100975     /**
100976      * @cfg {String} selectedCls
100977      * The class to apply to the selected cell. Defaults to <tt>'x-datepicker-selected'</tt>
100978      */
100979
100980     /**
100981      * @cfg {String} disabledCellCls
100982      * The class to apply to disabled cells. Defaults to <tt>'x-datepicker-disabled'</tt>
100983      */
100984
100985     /**
100986      * @cfg {String} longDayFormat
100987      * The format for displaying a date in a longer format. Defaults to <tt>'F d, Y'</tt>
100988      */
100989     longDayFormat: 'F d, Y',
100990
100991     /**
100992      * @cfg {Object} keyNavConfig Specifies optional custom key event handlers for the {@link Ext.util.KeyNav}
100993      * attached to this date picker. Must conform to the config format recognized by the {@link Ext.util.KeyNav}
100994      * constructor. Handlers specified in this object will replace default handlers of the same name.
100995      */
100996
100997     /**
100998      * @cfg {Boolean} focusOnShow
100999      * True to automatically focus the picker on show. Defaults to <tt>false</tt>.
101000      */
101001     focusOnShow: false,
101002
101003     // private
101004     // Set by other components to stop the picker focus being updated when the value changes.
101005     focusOnSelect: true,
101006
101007     width: 178,
101008
101009     // default value used to initialise each date in the DatePicker
101010     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
101011     initHour: 12, // 24-hour format
101012
101013     numDays: 42,
101014
101015     // private, inherit docs
101016     initComponent : function() {
101017         var me = this,
101018             clearTime = Ext.Date.clearTime;
101019
101020         me.selectedCls = me.baseCls + '-selected';
101021         me.disabledCellCls = me.baseCls + '-disabled';
101022         me.prevCls = me.baseCls + '-prevday';
101023         me.activeCls = me.baseCls + '-active';
101024         me.nextCls = me.baseCls + '-prevday';
101025         me.todayCls = me.baseCls + '-today';
101026         me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
101027         this.callParent();
101028
101029         me.value = me.value ?
101030                  clearTime(me.value, true) : clearTime(new Date());
101031
101032         me.addEvents(
101033             /**
101034              * @event select
101035              * Fires when a date is selected
101036              * @param {DatePicker} this DatePicker
101037              * @param {Date} date The selected date
101038              */
101039             'select'
101040         );
101041
101042         me.initDisabledDays();
101043     },
101044
101045     // private, inherit docs
101046     onRender : function(container, position){
101047         /*
101048          * days array for looping through 6 full weeks (6 weeks * 7 days)
101049          * Note that we explicitly force the size here so the template creates
101050          * all the appropriate cells.
101051          */
101052
101053         var me = this,
101054             days = new Array(me.numDays),
101055             today = Ext.Date.format(new Date(), me.format);
101056
101057         Ext.applyIf(me, {
101058             renderData: {},
101059             renderSelectors: {}
101060         });
101061
101062         Ext.apply(me.renderData, {
101063             dayNames: me.dayNames,
101064             ariaTitle: me.ariaTitle,
101065             value: me.value,
101066             showToday: me.showToday,
101067             prevText: me.prevText,
101068             nextText: me.nextText,
101069             days: days
101070         });
101071         me.getTpl('renderTpl').longDayFormat = me.longDayFormat;
101072
101073         Ext.apply(me.renderSelectors, {
101074             eventEl: 'table.' + me.baseCls + '-inner',
101075             prevEl: '.' + me.baseCls + '-prev a',
101076             nextEl: '.' + me.baseCls + '-next a',
101077             middleBtnEl: '.' + me.baseCls + '-month',
101078             footerEl: '.' + me.baseCls + '-footer'
101079         });
101080
101081         this.callParent(arguments);
101082         me.el.unselectable();
101083
101084         me.cells = me.eventEl.select('tbody td');
101085         me.textNodes = me.eventEl.query('tbody td span');
101086
101087         me.monthBtn = Ext.create('Ext.button.Split', {
101088             text: '',
101089             tooltip: me.monthYearText,
101090             renderTo: me.middleBtnEl
101091         });
101092         //~ me.middleBtnEl.down('button').addCls(Ext.baseCSSPrefix + 'btn-arrow');
101093
101094
101095         me.todayBtn = Ext.create('Ext.button.Button', {
101096             renderTo: me.footerEl,
101097             text: Ext.String.format(me.todayText, today),
101098             tooltip: Ext.String.format(me.todayTip, today),
101099             handler: me.selectToday,
101100             scope: me
101101         });
101102     },
101103
101104     // private, inherit docs
101105     initEvents: function(){
101106         var me = this,
101107             eDate = Ext.Date,
101108             day = eDate.DAY;
101109
101110         this.callParent();
101111
101112         me.prevRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
101113             handler: me.showPrevMonth,
101114             scope: me,
101115             preventDefault: true,
101116             stopDefault: true
101117         });
101118
101119         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
101120             handler: me.showNextMonth,
101121             scope: me,
101122             preventDefault:true,
101123             stopDefault:true
101124         });
101125
101126         me.keyNav = Ext.create('Ext.util.KeyNav', me.eventEl, Ext.apply({
101127             scope: me,
101128             'left' : function(e){
101129                 if(e.ctrlKey){
101130                     me.showPrevMonth();
101131                 }else{
101132                     me.update(eDate.add(me.activeDate, day, -1));
101133                 }
101134             },
101135
101136             'right' : function(e){
101137                 if(e.ctrlKey){
101138                     me.showNextMonth();
101139                 }else{
101140                     me.update(eDate.add(me.activeDate, day, 1));
101141                 }
101142             },
101143
101144             'up' : function(e){
101145                 if(e.ctrlKey){
101146                     me.showNextYear();
101147                 }else{
101148                     me.update(eDate.add(me.activeDate, day, -7));
101149                 }
101150             },
101151
101152             'down' : function(e){
101153                 if(e.ctrlKey){
101154                     me.showPrevYear();
101155                 }else{
101156                     me.update(eDate.add(me.activeDate, day, 7));
101157                 }
101158             },
101159             'pageUp' : me.showNextMonth,
101160             'pageDown' : me.showPrevMonth,
101161             'enter' : function(e){
101162                 e.stopPropagation();
101163                 return true;
101164             }
101165         }, me.keyNavConfig));
101166
101167         if(me.showToday){
101168             me.todayKeyListener = me.eventEl.addKeyListener(Ext.EventObject.SPACE, me.selectToday,  me);
101169         }
101170         me.mon(me.eventEl, 'mousewheel', me.handleMouseWheel, me);
101171         me.mon(me.eventEl, 'click', me.handleDateClick,  me, {delegate: 'a.' + me.baseCls + '-date'});
101172         me.mon(me.monthBtn, 'click', me.showMonthPicker, me);
101173         me.mon(me.monthBtn, 'arrowclick', me.showMonthPicker, me);
101174         me.update(me.value);
101175     },
101176
101177     /**
101178      * Setup the disabled dates regex based on config options
101179      * @private
101180      */
101181     initDisabledDays : function(){
101182         var me = this,
101183             dd = me.disabledDates,
101184             re = '(?:',
101185             len;
101186
101187         if(!me.disabledDatesRE && dd){
101188                 len = dd.length - 1;
101189
101190             Ext.each(dd, function(d, i){
101191                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(d, me.format)) + '$' : dd[i];
101192                 if(i != len){
101193                     re += '|';
101194                 }
101195             }, me);
101196             me.disabledDatesRE = new RegExp(re + ')');
101197         }
101198     },
101199
101200     /**
101201      * Replaces any existing disabled dates with new values and refreshes the DatePicker.
101202      * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config
101203      * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
101204      * @return {Ext.picker.Date} this
101205      */
101206     setDisabledDates : function(dd){
101207         var me = this;
101208
101209         if(Ext.isArray(dd)){
101210             me.disabledDates = dd;
101211             me.disabledDatesRE = null;
101212         }else{
101213             me.disabledDatesRE = dd;
101214         }
101215         me.initDisabledDays();
101216         me.update(me.value, true);
101217         return me;
101218     },
101219
101220     /**
101221      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
101222      * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config
101223      * for details on supported values.
101224      * @return {Ext.picker.Date} this
101225      */
101226     setDisabledDays : function(dd){
101227         this.disabledDays = dd;
101228         return this.update(this.value, true);
101229     },
101230
101231     /**
101232      * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
101233      * @param {Date} value The minimum date that can be selected
101234      * @return {Ext.picker.Date} this
101235      */
101236     setMinDate : function(dt){
101237         this.minDate = dt;
101238         return this.update(this.value, true);
101239     },
101240
101241     /**
101242      * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
101243      * @param {Date} value The maximum date that can be selected
101244      * @return {Ext.picker.Date} this
101245      */
101246     setMaxDate : function(dt){
101247         this.maxDate = dt;
101248         return this.update(this.value, true);
101249     },
101250
101251     /**
101252      * Sets the value of the date field
101253      * @param {Date} value The date to set
101254      * @return {Ext.picker.Date} this
101255      */
101256     setValue : function(value){
101257         this.value = Ext.Date.clearTime(value, true);
101258         return this.update(this.value);
101259     },
101260
101261     /**
101262      * Gets the current selected value of the date field
101263      * @return {Date} The selected date
101264      */
101265     getValue : function(){
101266         return this.value;
101267     },
101268
101269     // private
101270     focus : function(){
101271         this.update(this.activeDate);
101272     },
101273
101274     // private, inherit docs
101275     onEnable: function(){
101276         this.callParent();
101277         this.setDisabledStatus(false);
101278         this.update(this.activeDate);
101279
101280     },
101281
101282     // private, inherit docs
101283     onDisable : function(){
101284         this.callParent();
101285         this.setDisabledStatus(true);
101286     },
101287
101288     /**
101289      * Set the disabled state of various internal components
101290      * @private
101291      * @param {Boolean} disabled
101292      */
101293     setDisabledStatus : function(disabled){
101294         var me = this;
101295
101296         me.keyNav.setDisabled(disabled);
101297         me.prevRepeater.setDisabled(disabled);
101298         me.nextRepeater.setDisabled(disabled);
101299         if (me.showToday) {
101300             me.todayKeyListener.setDisabled(disabled);
101301             me.todayBtn.setDisabled(disabled);
101302         }
101303     },
101304
101305     /**
101306      * Get the current active date.
101307      * @private
101308      * @return {Date} The active date
101309      */
101310     getActive: function(){
101311         return this.activeDate || me.value;
101312     },
101313
101314     /**
101315      * Run any animation required to hide/show the month picker.
101316      * @private
101317      * @param {Boolean} isHide True if it's a hide operation
101318      */
101319     runAnimation: function(isHide){
101320         var options = {
101321                 target: this.monthPicker,
101322                 duration: 200
101323             };
101324
101325         Ext.fx.Manager.run();
101326         if (isHide) {
101327             //TODO: slideout
101328         } else {
101329             //TODO: slidein
101330         }
101331         Ext.create('Ext.fx.Anim', options);
101332     },
101333
101334     /**
101335      * Hides the month picker, if it's visible.
101336      * @return {Ext.picker.Date} this
101337      */
101338     hideMonthPicker : function(){
101339         var me = this,
101340             picker = me.monthPicker;
101341
101342         if (picker) {
101343             if (me.disableAnim) {
101344                 picker.hide();
101345             } else {
101346                 this.runAnimation(true);
101347             }
101348         }
101349         return me;
101350     },
101351
101352     /**
101353      * Show the month picker
101354      * @return {Ext.picker.Date} this
101355      */
101356     showMonthPicker : function(){
101357
101358         var me = this,
101359             picker,
101360             size,
101361             top,
101362             left;
101363
101364
101365         if (me.rendered && !me.disabled) {
101366             size = me.getSize();
101367             picker = me.createMonthPicker();
101368             picker.show();
101369             picker.setSize(size);
101370             picker.setValue(me.getActive());
101371
101372             if (me.disableAnim) {
101373                 picker.setPosition(-1, -1);
101374             } else {
101375                 me.runAnimation(false);
101376             }
101377         }
101378         return me;
101379     },
101380
101381     /**
101382      * Create the month picker instance
101383      * @private
101384      * @return {Ext.picker.Month} picker
101385      */
101386     createMonthPicker: function(){
101387         var me = this,
101388             picker = me.monthPicker;
101389
101390         if (!picker) {
101391             me.monthPicker = picker = Ext.create('Ext.picker.Month', {
101392                 renderTo: me.el,
101393                 floating: true,
101394                 shadow: false,
101395                 listeners: {
101396                     scope: me,
101397                     cancelclick: me.onCancelClick,
101398                     okclick: me.onOkClick,
101399                     yeardblclick: me.onOkClick,
101400                     monthdblclick: me.onOkClick
101401                 }
101402             });
101403
101404             me.on('beforehide', me.hideMonthPicker, me);
101405         }
101406         return picker;
101407     },
101408
101409     /**
101410      * Respond to an ok click on the month picker
101411      * @private
101412      */
101413     onOkClick: function(picker, value){
101414         var me = this,
101415             month = value[0],
101416             year = value[1],
101417             date = new Date(year, month, me.getActive().getDate());
101418
101419         if (date.getMonth() !== month) {
101420             // 'fix' the JS rolling date conversion if needed
101421             date = new Date(year, month, 1).getLastDateOfMonth();
101422         }
101423         me.update(date);
101424         me.hideMonthPicker();
101425     },
101426
101427     /**
101428      * Respond to a cancel click on the month picker
101429      * @private
101430      */
101431     onCancelClick: function(){
101432         this.hideMonthPicker();
101433     },
101434
101435     /**
101436      * Show the previous month.
101437      * @return {Ext.picker.Date} this
101438      */
101439     showPrevMonth : function(e){
101440         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1));
101441     },
101442
101443     /**
101444      * Show the next month.
101445      * @return {Ext.picker.Date} this
101446      */
101447     showNextMonth : function(e){
101448         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1));
101449     },
101450
101451     /**
101452      * Show the previous year.
101453      * @return {Ext.picker.Date} this
101454      */
101455     showPrevYear : function(){
101456         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1));
101457     },
101458
101459     /**
101460      * Show the next year.
101461      * @return {Ext.picker.Date} this
101462      */
101463     showNextYear : function(){
101464         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1));
101465     },
101466
101467     /**
101468      * Respond to the mouse wheel event
101469      * @private
101470      * @param {Ext.EventObject} e
101471      */
101472     handleMouseWheel : function(e){
101473         e.stopEvent();
101474         if(!this.disabled){
101475             var delta = e.getWheelDelta();
101476             if(delta > 0){
101477                 this.showPrevMonth();
101478             } else if(delta < 0){
101479                 this.showNextMonth();
101480             }
101481         }
101482     },
101483
101484     /**
101485      * Respond to a date being clicked in the picker
101486      * @private
101487      * @param {Ext.EventObject} e
101488      * @param {HTMLElement} t
101489      */
101490     handleDateClick : function(e, t){
101491         var me = this,
101492             handler = me.handler;
101493
101494         e.stopEvent();
101495         if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){
101496             me.cancelFocus = me.focusOnSelect === false;
101497             me.setValue(new Date(t.dateValue));
101498             delete me.cancelFocus;
101499             me.fireEvent('select', me, me.value);
101500             if (handler) {
101501                 handler.call(me.scope || me, me, me.value);
101502             }
101503             // event handling is turned off on hide
101504             // when we are using the picker in a field
101505             // therefore onSelect comes AFTER the select
101506             // event.
101507             me.onSelect();
101508         }
101509     },
101510
101511     /**
101512      * Perform any post-select actions
101513      * @private
101514      */
101515     onSelect: function() {
101516         if (this.hideOnSelect) {
101517              this.hide();
101518          }
101519     },
101520
101521     /**
101522      * Sets the current value to today.
101523      * @return {Ext.picker.Date} this
101524      */
101525     selectToday : function(){
101526         var me = this,
101527             btn = me.todayBtn,
101528             handler = me.handler;
101529
101530         if(btn && !btn.disabled){
101531             me.setValue(Ext.Date.clearTime(new Date()));
101532             me.fireEvent('select', me, me.value);
101533             if (handler) {
101534                 handler.call(me.scope || me, me, me.value);
101535             }
101536             me.onSelect();
101537         }
101538         return me;
101539     },
101540
101541     /**
101542      * Update the selected cell
101543      * @private
101544      * @param {Date} date The new date
101545      * @param {Date} active The active date
101546      */
101547     selectedUpdate: function(date, active){
101548         var me = this,
101549             t = date.getTime(),
101550             cells = me.cells,
101551             cls = me.selectedCls;
101552
101553         cells.removeCls(cls);
101554         cells.each(function(c){
101555             if (c.dom.firstChild.dateValue == t) {
101556                 me.el.dom.setAttribute('aria-activedescendent', c.dom.id);
101557                 c.addCls(cls);
101558                 if(me.isVisible() && !me.cancelFocus){
101559                     Ext.fly(c.dom.firstChild).focus(50);
101560                 }
101561                 return false;
101562             }
101563         }, this);
101564     },
101565
101566     /**
101567      * Update the contents of the picker for a new month
101568      * @private
101569      * @param {Date} date The new date
101570      * @param {Date} active The active date
101571      */
101572     fullUpdate: function(date, active){
101573         var me = this,
101574             cells = me.cells.elements,
101575             textNodes = me.textNodes,
101576             disabledCls = me.disabledCellCls,
101577             eDate = Ext.Date,
101578             i = 0,
101579             extraDays = 0,
101580             visible = me.isVisible(),
101581             sel = +eDate.clearTime(date, true),
101582             today = +eDate.clearTime(new Date()),
101583             min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY,
101584             max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY,
101585             ddMatch = me.disabledDatesRE,
101586             ddText = me.disabledDatesText,
101587             ddays = me.disabledDays ? me.disabledDays.join('') : false,
101588             ddaysText = me.disabledDaysText,
101589             format = me.format,
101590             days = eDate.getDaysInMonth(date),
101591             firstOfMonth = eDate.getFirstDateOfMonth(date),
101592             startingPos = firstOfMonth.getDay() - me.startDay,
101593             previousMonth = eDate.add(date, eDate.MONTH, -1),
101594             longDayFormat = me.longDayFormat,
101595             prevStart,
101596             current,
101597             disableToday,
101598             tempDate,
101599             setCellClass,
101600             html,
101601             cls,
101602             formatValue,
101603             value;
101604
101605         if (startingPos < 0) {
101606             startingPos += 7;
101607         }
101608
101609         days += startingPos;
101610         prevStart = eDate.getDaysInMonth(previousMonth) - startingPos;
101611         current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour);
101612
101613         if (me.showToday) {
101614             tempDate = eDate.clearTime(new Date());
101615             disableToday = (tempDate < min || tempDate > max ||
101616                 (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) ||
101617                 (ddays && ddays.indexOf(tempDate.getDay()) != -1));
101618
101619             if (!me.disabled) {
101620                 me.todayBtn.setDisabled(disableToday);
101621                 me.todayKeyListener.setDisabled(disableToday);
101622             }
101623         }
101624
101625         setCellClass = function(cell){
101626             value = +eDate.clearTime(current, true);
101627             cell.title = eDate.format(current, longDayFormat);
101628             // store dateValue number as an expando
101629             cell.firstChild.dateValue = value;
101630             if(value == today){
101631                 cell.className += ' ' + me.todayCls;
101632                 cell.title = me.todayText;
101633             }
101634             if(value == sel){
101635                 cell.className += ' ' + me.selectedCls;
101636                 me.el.dom.setAttribute('aria-activedescendant', cell.id);
101637                 if (visible && me.floating) {
101638                     Ext.fly(cell.firstChild).focus(50);
101639                 }
101640             }
101641             // disabling
101642             if(value < min) {
101643                 cell.className = disabledCls;
101644                 cell.title = me.minText;
101645                 return;
101646             }
101647             if(value > max) {
101648                 cell.className = disabledCls;
101649                 cell.title = me.maxText;
101650                 return;
101651             }
101652             if(ddays){
101653                 if(ddays.indexOf(current.getDay()) != -1){
101654                     cell.title = ddaysText;
101655                     cell.className = disabledCls;
101656                 }
101657             }
101658             if(ddMatch && format){
101659                 formatValue = eDate.dateFormat(current, format);
101660                 if(ddMatch.test(formatValue)){
101661                     cell.title = ddText.replace('%0', formatValue);
101662                     cell.className = disabledCls;
101663                 }
101664             }
101665         };
101666
101667         for(; i < me.numDays; ++i) {
101668             if (i < startingPos) {
101669                 html = (++prevStart);
101670                 cls = me.prevCls;
101671             } else if (i >= days) {
101672                 html = (++extraDays);
101673                 cls = me.nextCls;
101674             } else {
101675                 html = i - startingPos + 1;
101676                 cls = me.activeCls;
101677             }
101678             textNodes[i].innerHTML = html;
101679             cells[i].className = cls;
101680             current.setDate(current.getDate() + 1);
101681             setCellClass(cells[i]);
101682         }
101683
101684         me.monthBtn.setText(me.monthNames[date.getMonth()] + ' ' + date.getFullYear());
101685     },
101686
101687     /**
101688      * Update the contents of the picker
101689      * @private
101690      * @param {Date} date The new date
101691      * @param {Boolean} forceRefresh True to force a full refresh
101692      */
101693     update : function(date, forceRefresh){
101694         var me = this,
101695             active = me.activeDate;
101696
101697         if (me.rendered) {
101698             me.activeDate = date;
101699             if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
101700                 me.selectedUpdate(date, active);
101701             } else {
101702                 me.fullUpdate(date, active);
101703             }
101704         }
101705         return me;
101706     },
101707
101708     // private, inherit docs
101709     beforeDestroy : function() {
101710         var me = this;
101711
101712         if (me.rendered) {
101713             Ext.destroy(
101714                 me.todayKeyListener,
101715                 me.keyNav,
101716                 me.monthPicker,
101717                 me.monthBtn,
101718                 me.nextRepeater,
101719                 me.prevRepeater,
101720                 me.todayBtn
101721             );
101722             delete me.textNodes;
101723             delete me.cells.elements;
101724         }
101725     },
101726
101727     // private, inherit docs
101728     onShow: function() {
101729         this.callParent(arguments);
101730         if (this.focusOnShow) {
101731             this.focus();
101732         }
101733     }
101734 },
101735
101736 // After dependencies have loaded:
101737 function() {
101738     var proto = this.prototype;
101739
101740     proto.monthNames = Ext.Date.monthNames;
101741
101742     proto.dayNames = Ext.Date.dayNames;
101743
101744     proto.format = Ext.Date.defaultFormat;
101745 });
101746
101747 /**
101748  * @class Ext.form.field.Date
101749  * @extends Ext.form.field.Picker
101750
101751 Provides a date input field with a {@link Ext.picker.Date date picker} dropdown and automatic date
101752 validation.
101753
101754 This field recognizes and uses the JavaScript Date object as its main {@link #value} type. In addition,
101755 it recognizes string values which are parsed according to the {@link #format} and/or {@link #altFormats}
101756 configs. These may be reconfigured to use date formats appropriate for the user's locale.
101757
101758 The field may be limited to a certain range of dates by using the {@link #minValue}, {@link #maxValue},
101759 {@link #disabledDays}, and {@link #disabledDates} config parameters. These configurations will be used both
101760 in the field's validation, and in the date picker dropdown by preventing invalid dates from being selected.
101761 {@img Ext.form.Date/Ext.form.Date.png Ext.form.Date component}
101762 #Example usage:#
101763
101764     Ext.create('Ext.form.Panel', {
101765         width: 300,
101766         bodyPadding: 10,
101767         title: 'Dates',
101768         items: [{
101769             xtype: 'datefield',
101770             anchor: '100%',
101771             fieldLabel: 'From',
101772             name: 'from_date',
101773             maxValue: new Date()  // limited to the current date or prior
101774         }, {
101775             xtype: 'datefield',
101776             anchor: '100%',
101777             fieldLabel: 'To',
101778             name: 'to_date',
101779             value: new Date()  // defaults to today
101780         }],
101781             renderTo: Ext.getBody()
101782     });
101783
101784 #Date Formats Examples#
101785
101786 This example shows a couple of different date format parsing scenarios. Both use custom date format
101787 configurations; the first one matches the configured `format` while the second matches the `altFormats`.
101788
101789     Ext.create('Ext.form.Panel', {
101790         renderTo: Ext.getBody(),
101791         width: 300,
101792         bodyPadding: 10,
101793         title: 'Dates',
101794         items: [{
101795             xtype: 'datefield',
101796             anchor: '100%',
101797             fieldLabel: 'Date',
101798             name: 'date',
101799             // The value matches the format; will be parsed and displayed using that format.
101800             format: 'm d Y',
101801             value: '2 4 1978'
101802         }, {
101803             xtype: 'datefield',
101804             anchor: '100%',
101805             fieldLabel: 'Date',
101806             name: 'date',
101807             // The value does not match the format, but does match an altFormat; will be parsed
101808             // using the altFormat and displayed using the format.
101809             format: 'm d Y',
101810             altFormats: 'm,d,Y|m.d.Y',
101811             value: '2.4.1978'
101812         }]
101813     });
101814
101815  * @constructor
101816  * Create a new Date field
101817  * @param {Object} config
101818  * 
101819  * @xtype datefield
101820  * @markdown
101821  * @docauthor Jason Johnston <jason@sencha.com>
101822  */
101823 Ext.define('Ext.form.field.Date', {
101824     extend:'Ext.form.field.Picker',
101825     alias: 'widget.datefield',
101826     requires: ['Ext.picker.Date'],
101827     alternateClassName: ['Ext.form.DateField', 'Ext.form.Date'],
101828
101829     /**
101830      * @cfg {String} format
101831      * The default date format string which can be overriden for localization support.  The format must be
101832      * valid according to {@link Ext.Date#parse} (defaults to <tt>'m/d/Y'</tt>).
101833      */
101834     format : "m/d/Y",
101835     /**
101836      * @cfg {String} altFormats
101837      * Multiple date formats separated by "<tt>|</tt>" to try when parsing a user input value and it
101838      * does not match the defined format (defaults to
101839      * <tt>'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'</tt>).
101840      */
101841     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",
101842     /**
101843      * @cfg {String} disabledDaysText
101844      * The tooltip to display when the date falls on a disabled day (defaults to <tt>'Disabled'</tt>)
101845      */
101846     disabledDaysText : "Disabled",
101847     /**
101848      * @cfg {String} disabledDatesText
101849      * The tooltip text to display when the date falls on a disabled date (defaults to <tt>'Disabled'</tt>)
101850      */
101851     disabledDatesText : "Disabled",
101852     /**
101853      * @cfg {String} minText
101854      * The error text to display when the date in the cell is before <tt>{@link #minValue}</tt> (defaults to
101855      * <tt>'The date in this field must be after {minValue}'</tt>).
101856      */
101857     minText : "The date in this field must be equal to or after {0}",
101858     /**
101859      * @cfg {String} maxText
101860      * The error text to display when the date in the cell is after <tt>{@link #maxValue}</tt> (defaults to
101861      * <tt>'The date in this field must be before {maxValue}'</tt>).
101862      */
101863     maxText : "The date in this field must be equal to or before {0}",
101864     /**
101865      * @cfg {String} invalidText
101866      * The error text to display when the date in the field is invalid (defaults to
101867      * <tt>'{value} is not a valid date - it must be in the format {format}'</tt>).
101868      */
101869     invalidText : "{0} is not a valid date - it must be in the format {1}",
101870     /**
101871      * @cfg {String} triggerCls
101872      * An additional CSS class used to style the trigger button.  The trigger will always get the
101873      * class <tt>'x-form-trigger'</tt> and <tt>triggerCls</tt> will be <b>appended</b> if specified
101874      * (defaults to <tt>'x-form-date-trigger'</tt> which displays a calendar icon).
101875      */
101876     triggerCls : Ext.baseCSSPrefix + 'form-date-trigger',
101877     /**
101878      * @cfg {Boolean} showToday
101879      * <tt>false</tt> to hide the footer area of the Date picker containing the Today button and disable
101880      * the keyboard handler for spacebar that selects the current date (defaults to <tt>true</tt>).
101881      */
101882     showToday : true,
101883     /**
101884      * @cfg {Date/String} minValue
101885      * The minimum allowed date. Can be either a Javascript date object or a string date in a
101886      * valid format (defaults to undefined).
101887      */
101888     /**
101889      * @cfg {Date/String} maxValue
101890      * The maximum allowed date. Can be either a Javascript date object or a string date in a
101891      * valid format (defaults to undefined).
101892      */
101893     /**
101894      * @cfg {Array} disabledDays
101895      * An array of days to disable, 0 based (defaults to undefined). Some examples:<pre><code>
101896 // disable Sunday and Saturday:
101897 disabledDays:  [0, 6]
101898 // disable weekdays:
101899 disabledDays: [1,2,3,4,5]
101900      * </code></pre>
101901      */
101902     /**
101903      * @cfg {Array} disabledDates
101904      * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular
101905      * expression so they are very powerful. Some examples:<pre><code>
101906 // disable these exact dates:
101907 disabledDates: ["03/08/2003", "09/16/2003"]
101908 // disable these days for every year:
101909 disabledDates: ["03/08", "09/16"]
101910 // only match the beginning (useful if you are using short years):
101911 disabledDates: ["^03/08"]
101912 // disable every day in March 2006:
101913 disabledDates: ["03/../2006"]
101914 // disable every day in every March:
101915 disabledDates: ["^03"]
101916      * </code></pre>
101917      * Note that the format of the dates included in the array should exactly match the {@link #format} config.
101918      * In order to support regular expressions, if you are using a {@link #format date format} that has "." in
101919      * it, you will have to escape the dot when restricting dates. For example: <tt>["03\\.08\\.03"]</tt>.
101920      */
101921     
101922     /**
101923      * @cfg {String} submitFormat The date format string which will be submitted to the server.  
101924      * The format must be valid according to {@link Ext.Date#parse} (defaults to <tt>{@link #format}</tt>).
101925      */
101926
101927     // in the absence of a time value, a default value of 12 noon will be used
101928     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
101929     initTime: '12', // 24 hour format
101930
101931     initTimeFormat: 'H',
101932
101933     matchFieldWidth: false,
101934     /**
101935      * @cfg {Number} startDay
101936      * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
101937      */
101938     startDay: 0,
101939     
101940     initComponent : function(){
101941         var me = this,
101942             isString = Ext.isString,
101943             min, max;
101944
101945         min = me.minValue;
101946         max = me.maxValue;
101947         if(isString(min)){
101948             me.minValue = me.parseDate(min);
101949         }
101950         if(isString(max)){
101951             me.maxValue = me.parseDate(max);
101952         }
101953         me.disabledDatesRE = null;
101954         me.initDisabledDays();
101955
101956         me.callParent();
101957     },
101958
101959     initValue: function() {
101960         var me = this,
101961             value = me.value;
101962
101963         // If a String value was supplied, try to convert it to a proper Date
101964         if (Ext.isString(value)) {
101965             me.value = me.rawToValue(value);
101966         }
101967
101968         me.callParent();
101969     },
101970
101971     // private
101972     initDisabledDays : function(){
101973         if(this.disabledDates){
101974             var dd = this.disabledDates,
101975                 len = dd.length - 1,
101976                 re = "(?:";
101977
101978             Ext.each(dd, function(d, i){
101979                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(d.dateFormat(this.format)) + '$' : dd[i];
101980                 if (i !== len) {
101981                     re += '|';
101982                 }
101983             }, this);
101984             this.disabledDatesRE = new RegExp(re + ')');
101985         }
101986     },
101987
101988     /**
101989      * Replaces any existing disabled dates with new values and refreshes the Date picker.
101990      * @param {Array} disabledDates An array of date strings (see the <tt>{@link #disabledDates}</tt> config
101991      * for details on supported values) used to disable a pattern of dates.
101992      */
101993     setDisabledDates : function(dd){
101994         var me = this,
101995             picker = me.picker;
101996             
101997         me.disabledDates = dd;
101998         me.initDisabledDays();
101999         if (picker) {
102000             picker.setDisabledDates(me.disabledDatesRE);
102001         }
102002     },
102003
102004     /**
102005      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the Date picker.
102006      * @param {Array} disabledDays An array of disabled day indexes. See the <tt>{@link #disabledDays}</tt>
102007      * config for details on supported values.
102008      */
102009     setDisabledDays : function(dd){
102010         var picker = this.picker;
102011             
102012         this.disabledDays = dd;
102013         if (picker) {
102014             picker.setDisabledDays(dd);
102015         }
102016     },
102017
102018     /**
102019      * Replaces any existing <tt>{@link #minValue}</tt> with the new value and refreshes the Date picker.
102020      * @param {Date} value The minimum date that can be selected
102021      */
102022     setMinValue : function(dt){
102023         var me = this,
102024             picker = me.picker,
102025             minValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
102026             
102027         me.minValue = minValue;
102028         if (picker) {
102029             picker.minText = Ext.String.format(me.minText, me.formatDate(me.minValue));
102030             picker.setMinDate(minValue);
102031         }
102032     },
102033
102034     /**
102035      * Replaces any existing <tt>{@link #maxValue}</tt> with the new value and refreshes the Date picker.
102036      * @param {Date} value The maximum date that can be selected
102037      */
102038     setMaxValue : function(dt){
102039         var me = this,
102040             picker = me.picker,
102041             maxValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
102042             
102043         me.maxValue = maxValue;
102044         if (picker) {
102045             picker.maxText = Ext.String.format(me.maxText, me.formatDate(me.maxValue));
102046             picker.setMaxDate(maxValue);
102047         }
102048     },
102049
102050     /**
102051      * Runs all of Date's validations and returns an array of any errors. Note that this first
102052      * runs Text's validations, so the returned array is an amalgamation of all field errors.
102053      * The additional validation checks are testing that the date format is valid, that the chosen
102054      * date is within the min and max date constraints set, that the date chosen is not in the disabledDates
102055      * regex and that the day chosed is not one of the disabledDays.
102056      * @param {Mixed} value The value to get errors for (defaults to the current field value)
102057      * @return {Array} All validation errors for this field
102058      */
102059     getErrors: function(value) {
102060         var me = this,
102061             format = Ext.String.format,
102062             clearTime = Ext.Date.clearTime,
102063             errors = me.callParent(arguments),
102064             disabledDays = me.disabledDays,
102065             disabledDatesRE = me.disabledDatesRE,
102066             minValue = me.minValue,
102067             maxValue = me.maxValue,
102068             len = disabledDays ? disabledDays.length : 0,
102069             i = 0,
102070             svalue,
102071             fvalue,
102072             day,
102073             time;
102074
102075         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
102076
102077         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
102078              return errors;
102079         }
102080
102081         svalue = value;
102082         value = me.parseDate(value);
102083         if (!value) {
102084             errors.push(format(me.invalidText, svalue, me.format));
102085             return errors;
102086         }
102087
102088         time = value.getTime();
102089         if (minValue && time < clearTime(minValue).getTime()) {
102090             errors.push(format(me.minText, me.formatDate(minValue)));
102091         }
102092
102093         if (maxValue && time > clearTime(maxValue).getTime()) {
102094             errors.push(format(me.maxText, me.formatDate(maxValue)));
102095         }
102096
102097         if (disabledDays) {
102098             day = value.getDay();
102099
102100             for(; i < len; i++) {
102101                 if (day === disabledDays[i]) {
102102                     errors.push(me.disabledDaysText);
102103                     break;
102104                 }
102105             }
102106         }
102107
102108         fvalue = me.formatDate(value);
102109         if (disabledDatesRE && disabledDatesRE.test(fvalue)) {
102110             errors.push(format(me.disabledDatesText, fvalue));
102111         }
102112
102113         return errors;
102114     },
102115
102116     rawToValue: function(rawValue) {
102117         return this.parseDate(rawValue) || rawValue || null;
102118     },
102119
102120     valueToRaw: function(value) {
102121         return this.formatDate(this.parseDate(value));
102122     },
102123
102124     /**
102125      * Sets the value of the date field.  You can pass a date object or any string that can be
102126      * parsed into a valid date, using <tt>{@link #format}</tt> as the date format, according
102127      * to the same rules as {@link Ext.Date#parse} (the default format used is <tt>"m/d/Y"</tt>).
102128      * <br />Usage:
102129      * <pre><code>
102130 //All of these calls set the same date value (May 4, 2006)
102131
102132 //Pass a date object:
102133 var dt = new Date('5/4/2006');
102134 dateField.setValue(dt);
102135
102136 //Pass a date string (default format):
102137 dateField.setValue('05/04/2006');
102138
102139 //Pass a date string (custom format):
102140 dateField.format = 'Y-m-d';
102141 dateField.setValue('2006-05-04');
102142 </code></pre>
102143      * @param {String/Date} date The date or valid date string
102144      * @return {Ext.form.field.Date} this
102145      * @method setValue
102146      */
102147
102148     /**
102149      * Attempts to parse a given string value using a given {@link Ext.Date#parse date format}.
102150      * @param {String} value The value to attempt to parse
102151      * @param {String} format A valid date format (see {@link Ext.Date#parse})
102152      * @return {Date} The parsed Date object, or null if the value could not be successfully parsed.
102153      */
102154     safeParse : function(value, format) {
102155         var me = this,
102156             utilDate = Ext.Date,
102157             parsedDate,
102158             result = null;
102159             
102160         if (utilDate.formatContainsHourInfo(format)) {
102161             // if parse format contains hour information, no DST adjustment is necessary
102162             result = utilDate.parse(value, format);
102163         } else {
102164             // set time to 12 noon, then clear the time
102165             parsedDate = utilDate.parse(value + ' ' + me.initTime, format + ' ' + me.initTimeFormat);
102166             if (parsedDate) {
102167                 result = utilDate.clearTime(parsedDate);
102168             }
102169         }
102170         return result;
102171     },
102172     
102173     // @private
102174     getSubmitValue: function() {
102175         var me = this,
102176             format = me.submitFormat || me.format,
102177             value = me.getValue();
102178             
102179         return value ? Ext.Date.format(value, format) : null;
102180     },
102181
102182     /**
102183      * @private
102184      */
102185     parseDate : function(value) {
102186         if(!value || Ext.isDate(value)){
102187             return value;
102188         }
102189
102190         var me = this,
102191             val = me.safeParse(value, me.format),
102192             altFormats = me.altFormats,
102193             altFormatsArray = me.altFormatsArray,
102194             i = 0,
102195             len;
102196
102197         if (!val && altFormats) {
102198             altFormatsArray = altFormatsArray || altFormats.split('|');
102199             len = altFormatsArray.length;
102200             for (; i < len && !val; ++i) {
102201                 val = me.safeParse(value, altFormatsArray[i]);
102202             }
102203         }
102204         return val;
102205     },
102206
102207     // private
102208     formatDate : function(date){
102209         return Ext.isDate(date) ? Ext.Date.dateFormat(date, this.format) : date;
102210     },
102211
102212     createPicker: function() {
102213         var me = this,
102214             format = Ext.String.format;
102215
102216         return Ext.create('Ext.picker.Date', {
102217             ownerCt: me.ownerCt,
102218             renderTo: document.body,
102219             floating: true,
102220             hidden: true,
102221             focusOnShow: true,
102222             minDate: me.minValue,
102223             maxDate: me.maxValue,
102224             disabledDatesRE: me.disabledDatesRE,
102225             disabledDatesText: me.disabledDatesText,
102226             disabledDays: me.disabledDays,
102227             disabledDaysText: me.disabledDaysText,
102228             format: me.format,
102229             showToday: me.showToday,
102230             startDay: me.startDay,
102231             minText: format(me.minText, me.formatDate(me.minValue)),
102232             maxText: format(me.maxText, me.formatDate(me.maxValue)),
102233             listeners: {
102234                 scope: me,
102235                 select: me.onSelect
102236             },
102237             keyNavConfig: {
102238                 esc: function() {
102239                     me.collapse();
102240                 }
102241             }
102242         });
102243     },
102244
102245     onSelect: function(m, d) {
102246         var me = this;
102247         
102248         me.setValue(d);
102249         me.fireEvent('select', me, d);
102250         me.collapse();
102251     },
102252
102253     /**
102254      * @private
102255      * Sets the Date picker's value to match the current field value when expanding.
102256      */
102257     onExpand: function() {
102258         var me = this,
102259             value = me.getValue();
102260         me.picker.setValue(Ext.isDate(value) ? value : new Date());
102261     },
102262
102263     /**
102264      * @private
102265      * Focuses the field when collapsing the Date picker.
102266      */
102267     onCollapse: function() {
102268         this.focus(false, 60);
102269     },
102270
102271     // private
102272     beforeBlur : function(){
102273         var me = this,
102274             v = me.parseDate(me.getRawValue()),
102275             focusTask = me.focusTask;
102276         
102277         if (focusTask) {
102278             focusTask.cancel();
102279         }
102280         
102281         if (v) {
102282             me.setValue(v);
102283         }
102284     }
102285
102286     /**
102287      * @cfg {Boolean} grow @hide
102288      */
102289     /**
102290      * @cfg {Number} growMin @hide
102291      */
102292     /**
102293      * @cfg {Number} growMax @hide
102294      */
102295     /**
102296      * @hide
102297      * @method autoSize
102298      */
102299 });
102300
102301 /**
102302  * @class Ext.form.field.Display
102303  * @extends Ext.form.field.Base
102304  * <p>A display-only text field which is not validated and not submitted. This is useful for when you want
102305  * to display a value from a form's {@link Ext.form.Basic#load loaded data} but do not want to allow the
102306  * user to edit or submit that value. The value can be optionally {@link #htmlEncode HTML encoded} if it contains
102307  * HTML markup that you do not want to be rendered.</p>
102308  * <p>If you have more complex content, or need to include components within the displayed content, also
102309  * consider using a {@link Ext.form.FieldContainer} instead.</p>
102310  * {@img Ext.form.Display/Ext.form.Display.png Ext.form.Display component}
102311  * <p>Example:</p>
102312  * <pre><code>
102313     Ext.create('Ext.form.Panel', {
102314         width: 175,
102315         height: 120,
102316         bodyPadding: 10,
102317         title: 'Final Score',
102318         items: [{
102319             xtype: 'displayfield',
102320             fieldLabel: 'Home',
102321             name: 'home_score',
102322             value: '10'
102323         }, {
102324             xtype: 'displayfield',
102325             fieldLabel: 'Visitor',
102326             name: 'visitor_score',
102327             value: '11'
102328         }],
102329         buttons: [{
102330             text: 'Update',
102331         }],
102332         renderTo: Ext.getBody()
102333     });
102334 </code></pre>
102335
102336  * @constructor
102337  * Creates a new DisplayField.
102338  * @param {Object} config Configuration options
102339  *
102340  * @xtype displayfield
102341  */
102342 Ext.define('Ext.form.field.Display', {
102343     extend:'Ext.form.field.Base',
102344     alias: 'widget.displayfield',
102345     requires: ['Ext.util.Format', 'Ext.XTemplate'],
102346     alternateClassName: ['Ext.form.DisplayField', 'Ext.form.Display'],
102347     fieldSubTpl: [
102348         '<div id="{id}" class="{fieldCls}"></div>',
102349         {
102350             compiled: true,
102351             disableFormats: true
102352         }
102353     ],
102354
102355     /**
102356      * @cfg {String} fieldCls The default CSS class for the field (defaults to <tt>"x-form-display-field"</tt>)
102357      */
102358     fieldCls: Ext.baseCSSPrefix + 'form-display-field',
102359
102360     /**
102361      * @cfg {Boolean} htmlEncode <tt>false</tt> to skip HTML-encoding the text when rendering it (defaults to
102362      * <tt>false</tt>). This might be useful if you want to include tags in the field's innerHTML rather than
102363      * rendering them as string literals per the default logic.
102364      */
102365     htmlEncode: false,
102366
102367     validateOnChange: false,
102368
102369     initEvents: Ext.emptyFn,
102370
102371     submitValue: false,
102372
102373     isValid: function() {
102374         return true;
102375     },
102376
102377     validate: function() {
102378         return true;
102379     },
102380
102381     getRawValue: function() {
102382         return this.rawValue;
102383     },
102384
102385     setRawValue: function(value) {
102386         var me = this;
102387         value = Ext.value(value, '');
102388         me.rawValue = value;
102389         if (me.rendered) {
102390             me.inputEl.dom.innerHTML = me.htmlEncode ? Ext.util.Format.htmlEncode(value) : value;
102391         }
102392         return value;
102393     },
102394
102395     // private
102396     getContentTarget: function() {
102397         return this.inputEl;
102398     }
102399
102400     /**
102401      * @cfg {String} inputType
102402      * @hide
102403      */
102404     /**
102405      * @cfg {Boolean} disabled
102406      * @hide
102407      */
102408     /**
102409      * @cfg {Boolean} readOnly
102410      * @hide
102411      */
102412     /**
102413      * @cfg {Boolean} validateOnChange
102414      * @hide
102415      */
102416     /**
102417      * @cfg {Number} checkChangeEvents
102418      * @hide
102419      */
102420     /**
102421      * @cfg {Number} checkChangeBuffer
102422      * @hide
102423      */
102424 });
102425
102426 /**
102427  * @class Ext.form.field.File
102428  * @extends Ext.form.field.Text
102429
102430 A file upload field which has custom styling and allows control over the button text and other
102431 features of {@link Ext.form.field.Text text fields} like {@link Ext.form.field.Text#emptyText empty text}.
102432 It uses a hidden file input element behind the scenes to allow user selection of a file and to
102433 perform the actual upload during {@link Ext.form.Basic#submit form submit}.
102434
102435 Because there is no secure cross-browser way to programmatically set the value of a file input,
102436 the standard Field `setValue` method is not implemented. The `{@link #getValue}` method will return
102437 a value that is browser-dependent; some have just the file name, some have a full path, some use
102438 a fake path.
102439 {@img Ext.form.File/Ext.form.File.png Ext.form.File component}
102440 #Example Usage:#
102441
102442     Ext.create('Ext.form.Panel', {
102443         title: 'Upload a Photo',
102444         width: 400,
102445         bodyPadding: 10,
102446         frame: true,
102447         renderTo: Ext.getBody(),    
102448         items: [{
102449             xtype: 'filefield',
102450             name: 'photo',
102451             fieldLabel: 'Photo',
102452             labelWidth: 50,
102453             msgTarget: 'side',
102454             allowBlank: false,
102455             anchor: '100%',
102456             buttonText: 'Select Photo...'
102457         }],
102458     
102459         buttons: [{
102460             text: 'Upload',
102461             handler: function() {
102462                 var form = this.up('form').getForm();
102463                 if(form.isValid()){
102464                     form.submit({
102465                         url: 'photo-upload.php',
102466                         waitMsg: 'Uploading your photo...',
102467                         success: function(fp, o) {
102468                             Ext.Msg.alert('Success', 'Your photo "' + o.result.file + '" has been uploaded.');
102469                         }
102470                     });
102471                 }
102472             }
102473         }]
102474     });
102475
102476  * @constructor
102477  * Create a new File field
102478  * @param {Object} config Configuration options
102479  * @xtype filefield
102480  * @markdown
102481  * @docauthor Jason Johnston <jason@sencha.com>
102482  */
102483 Ext.define("Ext.form.field.File", {
102484     extend: 'Ext.form.field.Text',
102485     alias: ['widget.filefield', 'widget.fileuploadfield'],
102486     alternateClassName: ['Ext.form.FileUploadField', 'Ext.ux.form.FileUploadField', 'Ext.form.File'],
102487     uses: ['Ext.button.Button', 'Ext.layout.component.field.File'],
102488
102489     /**
102490      * @cfg {String} buttonText The button text to display on the upload button (defaults to
102491      * 'Browse...').  Note that if you supply a value for {@link #buttonConfig}, the buttonConfig.text
102492      * value will be used instead if available.
102493      */
102494     buttonText: 'Browse...',
102495
102496     /**
102497      * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible
102498      * text field (defaults to false).  If true, all inherited Text members will still be available.
102499      */
102500     buttonOnly: false,
102501
102502     /**
102503      * @cfg {Number} buttonMargin The number of pixels of space reserved between the button and the text field
102504      * (defaults to 3).  Note that this only applies if {@link #buttonOnly} = false.
102505      */
102506     buttonMargin: 3,
102507
102508     /**
102509      * @cfg {Object} buttonConfig A standard {@link Ext.button.Button} config object.
102510      */
102511
102512     /**
102513      * @event change
102514      * Fires when the underlying file input field's value has changed from the user
102515      * selecting a new file from the system file selection dialog.
102516      * @param {Ext.ux.form.FileUploadField} this
102517      * @param {String} value The file value returned by the underlying file input field
102518      */
102519
102520     /**
102521      * @property fileInputEl
102522      * @type {Ext.core.Element}
102523      * A reference to the invisible file input element created for this upload field. Only
102524      * populated after this component is rendered.
102525      */
102526
102527     /**
102528      * @property button
102529      * @type {Ext.button.Button}
102530      * A reference to the trigger Button component created for this upload field. Only
102531      * populated after this component is rendered.
102532      */
102533
102534     /**
102535      * @cfg {String} fieldBodyCls
102536      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
102537      * Defaults to 'x-form-file-wrap' for file upload field.
102538      */
102539     fieldBodyCls: Ext.baseCSSPrefix + 'form-file-wrap',
102540
102541
102542     // private
102543     readOnly: true,
102544     componentLayout: 'filefield',
102545
102546     // private
102547     onRender: function() {
102548         var me = this,
102549             inputEl;
102550
102551         me.callParent(arguments);
102552
102553         me.createButton();
102554         me.createFileInput();
102555         
102556         // we don't create the file/button til after onRender, the initial disable() is
102557         // called in the onRender of the component.
102558         if (me.disabled) {
102559             me.disableItems();
102560         }
102561
102562         inputEl = me.inputEl;
102563         inputEl.dom.removeAttribute('name'); //name goes on the fileInput, not the text input
102564         if (me.buttonOnly) {
102565             inputEl.setDisplayed(false);
102566         }
102567     },
102568
102569     /**
102570      * @private
102571      * Creates the custom trigger Button component. The fileInput will be inserted into this.
102572      */
102573     createButton: function() {
102574         var me = this;
102575         me.button = Ext.widget('button', Ext.apply({
102576             renderTo: me.bodyEl,
102577             text: me.buttonText,
102578             cls: Ext.baseCSSPrefix + 'form-file-btn',
102579             preventDefault: false,
102580             style: me.buttonOnly ? '' : 'margin-left:' + me.buttonMargin + 'px'
102581         }, me.buttonConfig));
102582     },
102583
102584     /**
102585      * @private
102586      * Creates the file input element. It is inserted into the trigger button component, made
102587      * invisible, and floated on top of the button's other content so that it will receive the
102588      * button's clicks.
102589      */
102590     createFileInput : function() {
102591         var me = this;
102592         me.fileInputEl = me.button.el.createChild({
102593             name: me.getName(),
102594             cls: Ext.baseCSSPrefix + 'form-file-input',
102595             tag: 'input',
102596             type: 'file',
102597             size: 1
102598         }).on('change', me.onFileChange, me);
102599     },
102600
102601     /**
102602      * @private Event handler fired when the user selects a file.
102603      */
102604     onFileChange: function() {
102605         this.lastValue = null; // force change event to get fired even if the user selects a file with the same name
102606         Ext.form.field.File.superclass.setValue.call(this, this.fileInputEl.dom.value);
102607     },
102608
102609     /**
102610      * Overridden to do nothing
102611      * @hide
102612      */
102613     setValue: Ext.emptyFn,
102614
102615     reset : function(){
102616         this.fileInputEl.remove();
102617         this.createFileInput();
102618         this.callParent();
102619     },
102620
102621     onDisable: function(){
102622         this.callParent();
102623         this.disableItems();
102624     },
102625     
102626     disableItems: function(){
102627         var file = this.fileInputEl,
102628             button = this.button;
102629              
102630         if (file) {
102631             file.dom.disabled = true;
102632         }
102633         if (button) {
102634             button.disable();
102635         }    
102636     },
102637
102638     onEnable: function(){
102639         var me = this;
102640         me.callParent();
102641         me.fileInputEl.dom.disabled = false;
102642         me.button.enable();
102643     },
102644
102645     isFileUpload: function() {
102646         return true;
102647     },
102648
102649     extractFileInput: function() {
102650         var fileInput = this.fileInputEl.dom;
102651         this.reset();
102652         return fileInput;
102653     },
102654
102655     onDestroy: function(){
102656         Ext.destroyMembers(this, 'fileInputEl', 'button');
102657         this.callParent();
102658     }
102659
102660
102661 });
102662
102663 /**
102664  * @class Ext.form.field.Hidden
102665  * @extends Ext.form.field.Base
102666  * <p>A basic hidden field for storing hidden values in forms that need to be passed in the form submit.</p>
102667  * <p>This creates an actual input element with type="submit" in the DOM. While its label is
102668  * {@link #hideLabel not rendered} by default, it is still a real component and may be sized according to
102669  * its owner container's layout.</p>
102670  * <p>Because of this, in most cases it is more convenient and less problematic to simply
102671  * {@link Ext.form.action.Action#params pass hidden parameters} directly when
102672  * {@link Ext.form.Basic#submit submitting the form}.</p>
102673  * <p>Example:</p>
102674  * <pre><code>new Ext.form.Panel({
102675     title: 'My Form',
102676     items: [{
102677         xtype: 'textfield',
102678         fieldLabel: 'Text Field',
102679         name: 'text_field',
102680         value: 'value from text field'
102681     }, {
102682         xtype: 'hiddenfield',
102683         name: 'hidden_field_1',
102684         value: 'value from hidden field'
102685     }],
102686
102687     buttons: [{
102688         text: 'Submit',
102689         handler: function() {
102690             this.up('form').getForm().submit({
102691                 params: {
102692                     hidden_field_2: 'value from submit call'
102693                 }
102694             });
102695         }
102696     }]
102697 });</code></pre>
102698  * <p>Submitting the above form will result in three values sent to the server:
102699  * <code>text_field=value+from+text+field&hidden_field_1=value+from+hidden+field&<br>hidden_field_2=value+from+submit+call</code></p>
102700  *
102701  * @constructor
102702  * Create a new Hidden field.
102703  * @param {Object} config Configuration options
102704  * 
102705  * @xtype hiddenfield
102706  */
102707 Ext.define('Ext.form.field.Hidden', {
102708     extend:'Ext.form.field.Base',
102709     alias: ['widget.hiddenfield', 'widget.hidden'],
102710     alternateClassName: 'Ext.form.Hidden',
102711
102712     // private
102713     inputType : 'hidden',
102714     hideLabel: true,
102715     
102716     initComponent: function(){
102717         this.formItemCls += '-hidden';
102718         this.callParent();    
102719     },
102720
102721     // These are all private overrides
102722     initEvents: Ext.emptyFn,
102723     setSize : Ext.emptyFn,
102724     setWidth : Ext.emptyFn,
102725     setHeight : Ext.emptyFn,
102726     setPosition : Ext.emptyFn,
102727     setPagePosition : Ext.emptyFn,
102728     markInvalid : Ext.emptyFn,
102729     clearInvalid : Ext.emptyFn
102730 });
102731
102732 /**
102733  * @class Ext.picker.Color
102734  * @extends Ext.Component
102735  * <p>ColorPicker provides a simple color palette for choosing colors. The picker can be rendered to any container.
102736  * The available default to a standard 40-color palette; this can be customized with the {@link #colors} config.</p>
102737  * <p>Typically you will need to implement a handler function to be notified when the user chooses a color from the
102738  * picker; you can register the handler using the {@link #select} event, or by implementing the {@link #handler}
102739  * method.</p>
102740  * <p>Here's an example of typical usage:</p>
102741  * <pre><code>var cp = new Ext.picker.Color({
102742     value: '993300',  // initial selected color
102743     renderTo: 'my-div'
102744 });
102745
102746 cp.on('select', function(picker, selColor){
102747     // do something with selColor
102748 });
102749 </code></pre>
102750  * {@img Ext.picker.Color/Ext.picker.Color.png Ext.picker.Color component}
102751  *
102752  * @constructor
102753  * Create a new ColorPicker
102754  * @param {Object} config The config object
102755  * 
102756  * @xtype colorpicker
102757  */
102758 Ext.define('Ext.picker.Color', {
102759     extend: 'Ext.Component',
102760     requires: 'Ext.XTemplate',
102761     alias: 'widget.colorpicker',
102762     alternateClassName: 'Ext.ColorPalette',
102763     
102764     /**
102765      * @cfg {String} componentCls
102766      * The CSS class to apply to the containing element (defaults to 'x-color-picker')
102767      */
102768     componentCls : Ext.baseCSSPrefix + 'color-picker',
102769     
102770     /**
102771      * @cfg {String} selectedCls
102772      * The CSS class to apply to the selected element
102773      */
102774     selectedCls: Ext.baseCSSPrefix + 'color-picker-selected',
102775     
102776     /**
102777      * @cfg {String} value
102778      * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol).  Note that
102779      * the hex codes are case-sensitive.
102780      */
102781     value : null,
102782     
102783     /**
102784      * @cfg {String} clickEvent
102785      * The DOM event that will cause a color to be selected. This can be any valid event name (dblclick, contextmenu).
102786      * Defaults to <tt>'click'</tt>.
102787      */
102788     clickEvent :'click',
102789
102790     /**
102791      * @cfg {Boolean} allowReselect If set to true then reselecting a color that is already selected fires the {@link #select} event
102792      */
102793     allowReselect : false,
102794
102795     /**
102796      * <p>An array of 6-digit color hex code strings (without the # symbol).  This array can contain any number
102797      * of colors, and each hex code should be unique.  The width of the picker is controlled via CSS by adjusting
102798      * the width property of the 'x-color-picker' class (or assigning a custom class), so you can balance the number
102799      * of colors with the width setting until the box is symmetrical.</p>
102800      * <p>You can override individual colors if needed:</p>
102801      * <pre><code>
102802 var cp = new Ext.picker.Color();
102803 cp.colors[0] = 'FF0000';  // change the first box to red
102804 </code></pre>
102805
102806 Or you can provide a custom array of your own for complete control:
102807 <pre><code>
102808 var cp = new Ext.picker.Color();
102809 cp.colors = ['000000', '993300', '333300'];
102810 </code></pre>
102811      * @type Array
102812      */
102813     colors : [
102814         '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
102815         '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
102816         'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
102817         'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
102818         'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
102819     ],
102820
102821     /**
102822      * @cfg {Function} handler
102823      * Optional. A function that will handle the select event of this picker.
102824      * The handler is passed the following parameters:<div class="mdetail-params"><ul>
102825      * <li><code>picker</code> : ColorPicker<div class="sub-desc">The {@link #picker Ext.picker.Color}.</div></li>
102826      * <li><code>color</code> : String<div class="sub-desc">The 6-digit color hex code (without the # symbol).</div></li>
102827      * </ul></div>
102828      */
102829     /**
102830      * @cfg {Object} scope
102831      * The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>
102832      * function will be called.  Defaults to this ColorPicker instance.
102833      */
102834     
102835     colorRe: /(?:^|\s)color-(.{6})(?:\s|$)/,
102836     
102837     constructor: function() {
102838         this.renderTpl = Ext.create('Ext.XTemplate', '<tpl for="colors"><a href="#" class="color-{.}" hidefocus="on"><em><span style="background:#{.}" unselectable="on">&#160;</span></em></a></tpl>');
102839         this.callParent(arguments);
102840     },
102841     
102842     // private
102843     initComponent : function(){
102844         var me = this;
102845         
102846         this.callParent(arguments);
102847         me.addEvents(
102848             /**
102849              * @event select
102850              * Fires when a color is selected
102851              * @param {Ext.picker.Color} this
102852              * @param {String} color The 6-digit color hex code (without the # symbol)
102853              */
102854             'select'
102855         );
102856
102857         if (me.handler) {
102858             me.on('select', me.handler, me.scope, true);
102859         }
102860     },
102861
102862
102863     // private
102864     onRender : function(container, position){
102865         var me = this,
102866             clickEvent = me.clickEvent;
102867             
102868         Ext.apply(me.renderData, {
102869             itemCls: me.itemCls,
102870             colors: me.colors    
102871         });
102872         this.callParent(arguments);
102873
102874         me.mon(me.el, clickEvent, me.handleClick, me, {delegate: 'a'});
102875         // always stop following the anchors
102876         if(clickEvent != 'click'){
102877             me.mon(me.el, 'click', Ext.emptyFn, me, {delegate: 'a', stopEvent: true});
102878         }
102879     },
102880
102881     // private
102882     afterRender : function(){
102883         var me = this,
102884             value;
102885             
102886         this.callParent(arguments);
102887         if (me.value) {
102888             value = me.value;
102889             me.value = null;
102890             me.select(value, true);
102891         }
102892     },
102893
102894     // private
102895     handleClick : function(event, target){
102896         var me = this,
102897             color;
102898             
102899         event.stopEvent();
102900         if (!me.disabled) {
102901             color = target.className.match(me.colorRe)[1];
102902             me.select(color.toUpperCase());
102903         }
102904     },
102905
102906     /**
102907      * Selects the specified color in the picker (fires the {@link #select} event)
102908      * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
102909      * @param {Boolean} suppressEvent (optional) True to stop the select event from firing. Defaults to <tt>false</tt>.
102910      */
102911     select : function(color, suppressEvent){
102912         
102913         var me = this,
102914             selectedCls = me.selectedCls,
102915             value = me.value,
102916             el;
102917             
102918         color = color.replace('#', '');
102919         if (!me.rendered) {
102920             me.value = color;
102921             return;
102922         }
102923         
102924         
102925         if (color != value || me.allowReselect) {
102926             el = me.el;
102927
102928             if (me.value) {
102929                 el.down('a.color-' + value).removeCls(selectedCls);
102930             }
102931             el.down('a.color-' + color).addCls(selectedCls);
102932             me.value = color;
102933             if (suppressEvent !== true) {
102934                 me.fireEvent('select', me, color);
102935             }
102936         }
102937     },
102938     
102939     /**
102940      * Get the currently selected color value.
102941      * @return {String} value The selected value. Null if nothing is selected.
102942      */
102943     getValue: function(){
102944         return this.value || null;
102945     }
102946 });
102947
102948 /**
102949  * @private
102950  * @class Ext.layout.component.field.HtmlEditor
102951  * @extends Ext.layout.component.field.Field
102952  * Layout class for {@link Ext.form.field.HtmlEditor} fields. Sizes the toolbar, textarea, and iframe elements.
102953  * @private
102954  */
102955
102956 Ext.define('Ext.layout.component.field.HtmlEditor', {
102957     extend: 'Ext.layout.component.field.Field',
102958     alias: ['layout.htmleditor'],
102959
102960     type: 'htmleditor',
102961
102962     sizeBodyContents: function(width, height) {
102963         var me = this,
102964             owner = me.owner,
102965             bodyEl = owner.bodyEl,
102966             toolbar = owner.getToolbar(),
102967             textarea = owner.textareaEl,
102968             iframe = owner.iframeEl,
102969             editorHeight;
102970
102971         if (Ext.isNumber(width)) {
102972             width -= bodyEl.getFrameWidth('lr');
102973         }
102974         toolbar.setWidth(width);
102975         textarea.setWidth(width);
102976         iframe.setWidth(width);
102977
102978         // If fixed height, subtract toolbar height from the input area height
102979         if (Ext.isNumber(height)) {
102980             editorHeight = height - toolbar.getHeight() - bodyEl.getFrameWidth('tb');
102981             textarea.setHeight(editorHeight);
102982             iframe.setHeight(editorHeight);
102983         }
102984     }
102985 });
102986 /**
102987  * @class Ext.form.field.HtmlEditor
102988  * @extends Ext.Component
102989  *
102990  * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
102991  * automatically hidden when needed. These are noted in the config options where appropriate.
102992  * 
102993  * The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
102994  * enabled by default unless the global {@link Ext.tip.QuickTipManager} singleton is {@link Ext.tip.QuickTipManager#init initialized}.
102995  * 
102996  * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
102997  * any element that has display set to 'none' can cause problems in Safari and Firefox due to their default iframe reloading bugs.
102998  *
102999  * {@img Ext.form.HtmlEditor/Ext.form.HtmlEditor1.png Ext.form.HtmlEditor component}
103000  *
103001  * ## Example usage
103002  *
103003  * {@img Ext.form.HtmlEditor/Ext.form.HtmlEditor2.png Ext.form.HtmlEditor component}
103004  *
103005  *     // Simple example rendered with default options:
103006  *     Ext.tip.QuickTips.init();  // enable tooltips
103007  *     Ext.create('Ext.form.HtmlEditor', {
103008  *         width: 580,
103009  *         height: 250,
103010  *         renderTo: Ext.getBody()        
103011  *     });
103012  * 
103013  * {@img Ext.form.HtmlEditor/Ext.form.HtmlEditor2.png Ext.form.HtmlEditor component}
103014  * 
103015  *     // Passed via xtype into a container and with custom options:
103016  *     Ext.tip.QuickTips.init();  // enable tooltips
103017  *     new Ext.panel.Panel({
103018  *         title: 'HTML Editor',
103019  *         renderTo: Ext.getBody(),
103020  *         width: 550,
103021  *         height: 250,
103022  *         frame: true,
103023  *         layout: 'fit',
103024  *         items: {
103025  *             xtype: 'htmleditor',
103026  *             enableColors: false,
103027  *             enableAlignments: false
103028  *         }
103029  *     });
103030  *
103031  * @constructor
103032  * Create a new HtmlEditor
103033  * @param {Object} config
103034  * @xtype htmleditor
103035  */
103036 Ext.define('Ext.form.field.HtmlEditor', {
103037     extend:'Ext.Component',
103038     mixins: {
103039         labelable: 'Ext.form.Labelable',
103040         field: 'Ext.form.field.Field'
103041     },
103042     alias: 'widget.htmleditor',
103043     alternateClassName: 'Ext.form.HtmlEditor',
103044     requires: [
103045         'Ext.tip.QuickTipManager',
103046         'Ext.picker.Color',
103047         'Ext.toolbar.Item',
103048         'Ext.toolbar.Toolbar',
103049         'Ext.util.Format',
103050         'Ext.layout.component.field.HtmlEditor'
103051     ],
103052
103053     fieldSubTpl: [
103054         '<div class="{toolbarWrapCls}"></div>',
103055         '<textarea id="{id}" name="{name}" tabIndex="-1" class="{textareaCls}" ',
103056             'style="{size}" autocomplete="off"></textarea>',
103057         '<iframe name="{iframeName}" frameBorder="0" style="overflow:auto;{size}" src="{iframeSrc}"></iframe>',
103058         {
103059             compiled: true,
103060             disableFormats: true
103061         }
103062     ],
103063
103064     /**
103065      * @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons (defaults to true)
103066      */
103067     enableFormat : true,
103068     /**
103069      * @cfg {Boolean} enableFontSize Enable the increase/decrease font size buttons (defaults to true)
103070      */
103071     enableFontSize : true,
103072     /**
103073      * @cfg {Boolean} enableColors Enable the fore/highlight color buttons (defaults to true)
103074      */
103075     enableColors : true,
103076     /**
103077      * @cfg {Boolean} enableAlignments Enable the left, center, right alignment buttons (defaults to true)
103078      */
103079     enableAlignments : true,
103080     /**
103081      * @cfg {Boolean} enableLists Enable the bullet and numbered list buttons. Not available in Safari. (defaults to true)
103082      */
103083     enableLists : true,
103084     /**
103085      * @cfg {Boolean} enableSourceEdit Enable the switch to source edit button. Not available in Safari. (defaults to true)
103086      */
103087     enableSourceEdit : true,
103088     /**
103089      * @cfg {Boolean} enableLinks Enable the create link button. Not available in Safari. (defaults to true)
103090      */
103091     enableLinks : true,
103092     /**
103093      * @cfg {Boolean} enableFont Enable font selection. Not available in Safari. (defaults to true)
103094      */
103095     enableFont : true,
103096     /**
103097      * @cfg {String} createLinkText The default text for the create link prompt
103098      */
103099     createLinkText : 'Please enter the URL for the link:',
103100     /**
103101      * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
103102      */
103103     defaultLinkValue : 'http:/'+'/',
103104     /**
103105      * @cfg {Array} fontFamilies An array of available font families
103106      */
103107     fontFamilies : [
103108         'Arial',
103109         'Courier New',
103110         'Tahoma',
103111         'Times New Roman',
103112         'Verdana'
103113     ],
103114     defaultFont: 'tahoma',
103115     /**
103116      * @cfg {String} defaultValue A default value to be put into the editor to resolve focus issues (defaults to &#160; (Non-breaking space) in Opera and IE6, &#8203; (Zero-width space) in all other browsers).
103117      */
103118     defaultValue: (Ext.isOpera || Ext.isIE6) ? '&#160;' : '&#8203;',
103119
103120     fieldBodyCls: Ext.baseCSSPrefix + 'html-editor-wrap',
103121
103122     componentLayout: 'htmleditor',
103123
103124     // private properties
103125     initialized : false,
103126     activated : false,
103127     sourceEditMode : false,
103128     iframePad:3,
103129     hideMode:'offsets',
103130
103131     maskOnDisable: true,
103132     
103133     // private
103134     initComponent : function(){
103135         var me = this;
103136
103137         me.addEvents(
103138             /**
103139              * @event initialize
103140              * Fires when the editor is fully initialized (including the iframe)
103141              * @param {Ext.form.field.HtmlEditor} this
103142              */
103143             'initialize',
103144             /**
103145              * @event activate
103146              * Fires when the editor is first receives the focus. Any insertion must wait
103147              * until after this event.
103148              * @param {Ext.form.field.HtmlEditor} this
103149              */
103150             'activate',
103151              /**
103152              * @event beforesync
103153              * Fires before the textarea is updated with content from the editor iframe. Return false
103154              * to cancel the sync.
103155              * @param {Ext.form.field.HtmlEditor} this
103156              * @param {String} html
103157              */
103158             'beforesync',
103159              /**
103160              * @event beforepush
103161              * Fires before the iframe editor is updated with content from the textarea. Return false
103162              * to cancel the push.
103163              * @param {Ext.form.field.HtmlEditor} this
103164              * @param {String} html
103165              */
103166             'beforepush',
103167              /**
103168              * @event sync
103169              * Fires when the textarea is updated with content from the editor iframe.
103170              * @param {Ext.form.field.HtmlEditor} this
103171              * @param {String} html
103172              */
103173             'sync',
103174              /**
103175              * @event push
103176              * Fires when the iframe editor is updated with content from the textarea.
103177              * @param {Ext.form.field.HtmlEditor} this
103178              * @param {String} html
103179              */
103180             'push',
103181              /**
103182              * @event editmodechange
103183              * Fires when the editor switches edit modes
103184              * @param {Ext.form.field.HtmlEditor} this
103185              * @param {Boolean} sourceEdit True if source edit, false if standard editing.
103186              */
103187             'editmodechange'
103188         );
103189
103190         me.callParent(arguments);
103191
103192         // Init mixins
103193         me.initLabelable();
103194         me.initField();
103195     },
103196
103197     /*
103198      * Protected method that will not generally be called directly. It
103199      * is called when the editor creates its toolbar. Override this method if you need to
103200      * add custom toolbar buttons.
103201      * @param {Ext.form.field.HtmlEditor} editor
103202      */
103203     createToolbar : function(editor){
103204         var me = this,
103205             items = [],
103206             tipsEnabled = Ext.tip.QuickTipManager && Ext.tip.QuickTipManager.isEnabled(),
103207             baseCSSPrefix = Ext.baseCSSPrefix,
103208             fontSelectItem, toolbar, undef;
103209
103210         function btn(id, toggle, handler){
103211             return {
103212                 itemId : id,
103213                 cls : baseCSSPrefix + 'btn-icon',
103214                 iconCls: baseCSSPrefix + 'edit-'+id,
103215                 enableToggle:toggle !== false,
103216                 scope: editor,
103217                 handler:handler||editor.relayBtnCmd,
103218                 clickEvent:'mousedown',
103219                 tooltip: tipsEnabled ? editor.buttonTips[id] || undef : undef,
103220                 overflowText: editor.buttonTips[id].title || undef,
103221                 tabIndex:-1
103222             };
103223         }
103224
103225
103226         if (me.enableFont && !Ext.isSafari2) {
103227             fontSelectItem = Ext.widget('component', {
103228                 renderTpl: [
103229                     '<select class="{cls}">',
103230                         '<tpl for="fonts">',
103231                             '<option value="{[values.toLowerCase()]}" style="font-family:{.}"<tpl if="values.toLowerCase()==parent.defaultFont"> selected</tpl>>{.}</option>',
103232                         '</tpl>',
103233                     '</select>'
103234                 ],
103235                 renderData: {
103236                     cls: baseCSSPrefix + 'font-select',
103237                     fonts: me.fontFamilies,
103238                     defaultFont: me.defaultFont
103239                 },
103240                 renderSelectors: {
103241                     selectEl: 'select'
103242                 },
103243                 onDisable: function() {
103244                     var selectEl = this.selectEl;
103245                     if (selectEl) {
103246                         selectEl.dom.disabled = true;
103247                     }
103248                     Ext.Component.superclass.onDisable.apply(this, arguments);
103249                 },
103250                 onEnable: function() {
103251                     var selectEl = this.selectEl;
103252                     if (selectEl) {
103253                         selectEl.dom.disabled = false;
103254                     }
103255                     Ext.Component.superclass.onEnable.apply(this, arguments);
103256                 }
103257             });
103258
103259             items.push(
103260                 fontSelectItem,
103261                 '-'
103262             );
103263         }
103264
103265         if (me.enableFormat) {
103266             items.push(
103267                 btn('bold'),
103268                 btn('italic'),
103269                 btn('underline')
103270             );
103271         }
103272
103273         if (me.enableFontSize) {
103274             items.push(
103275                 '-',
103276                 btn('increasefontsize', false, me.adjustFont),
103277                 btn('decreasefontsize', false, me.adjustFont)
103278             );
103279         }
103280
103281         if (me.enableColors) {
103282             items.push(
103283                 '-', {
103284                     itemId: 'forecolor',
103285                     cls: baseCSSPrefix + 'btn-icon',
103286                     iconCls: baseCSSPrefix + 'edit-forecolor',
103287                     overflowText: editor.buttonTips.forecolor.title,
103288                     tooltip: tipsEnabled ? editor.buttonTips.forecolor || undef : undef,
103289                     tabIndex:-1,
103290                     menu : Ext.widget('menu', {
103291                         plain: true,
103292                         items: [{
103293                             xtype: 'colorpicker',
103294                             allowReselect: true,
103295                             focus: Ext.emptyFn,
103296                             value: '000000',
103297                             plain: true,
103298                             clickEvent: 'mousedown',
103299                             handler: function(cp, color) {
103300                                 me.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
103301                                 me.deferFocus();
103302                                 this.up('menu').hide();
103303                             }
103304                         }]
103305                     })
103306                 }, {
103307                     itemId: 'backcolor',
103308                     cls: baseCSSPrefix + 'btn-icon',
103309                     iconCls: baseCSSPrefix + 'edit-backcolor',
103310                     overflowText: editor.buttonTips.backcolor.title,
103311                     tooltip: tipsEnabled ? editor.buttonTips.backcolor || undef : undef,
103312                     tabIndex:-1,
103313                     menu : Ext.widget('menu', {
103314                         plain: true,
103315                         items: [{
103316                             xtype: 'colorpicker',
103317                             focus: Ext.emptyFn,
103318                             value: 'FFFFFF',
103319                             plain: true,
103320                             allowReselect: true,
103321                             clickEvent: 'mousedown',
103322                             handler: function(cp, color) {
103323                                 if (Ext.isGecko) {
103324                                     me.execCmd('useCSS', false);
103325                                     me.execCmd('hilitecolor', color);
103326                                     me.execCmd('useCSS', true);
103327                                     me.deferFocus();
103328                                 } else {
103329                                     me.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
103330                                     me.deferFocus();
103331                                 }
103332                                 this.up('menu').hide();
103333                             }
103334                         }]
103335                     })
103336                 }
103337             );
103338         }
103339
103340         if (me.enableAlignments) {
103341             items.push(
103342                 '-',
103343                 btn('justifyleft'),
103344                 btn('justifycenter'),
103345                 btn('justifyright')
103346             );
103347         }
103348
103349         if (!Ext.isSafari2) {
103350             if (me.enableLinks) {
103351                 items.push(
103352                     '-',
103353                     btn('createlink', false, me.createLink)
103354                 );
103355             }
103356
103357             if (me.enableLists) {
103358                 items.push(
103359                     '-',
103360                     btn('insertorderedlist'),
103361                     btn('insertunorderedlist')
103362                 );
103363             }
103364             if (me.enableSourceEdit) {
103365                 items.push(
103366                     '-',
103367                     btn('sourceedit', true, function(btn){
103368                         me.toggleSourceEdit(!me.sourceEditMode);
103369                     })
103370                 );
103371             }
103372         }
103373
103374         // build the toolbar
103375         toolbar = Ext.widget('toolbar', {
103376             renderTo: me.toolbarWrap,
103377             enableOverflow: true,
103378             items: items
103379         });
103380
103381         if (fontSelectItem) {
103382             me.fontSelect = fontSelectItem.selectEl;
103383
103384             me.mon(me.fontSelect, 'change', function(){
103385                 me.relayCmd('fontname', me.fontSelect.dom.value);
103386                 me.deferFocus();
103387             });
103388         }
103389
103390         // stop form submits
103391         me.mon(toolbar.el, 'click', function(e){
103392             e.preventDefault();
103393         });
103394
103395         me.toolbar = toolbar;
103396     },
103397
103398     onDisable: function() {
103399         this.bodyEl.mask();
103400         this.callParent(arguments);
103401     },
103402
103403     onEnable: function() {
103404         this.bodyEl.unmask();
103405         this.callParent(arguments);
103406     },
103407
103408     /**
103409      * Sets the read only state of this field.
103410      * @param {Boolean} readOnly Whether the field should be read only.
103411      */
103412     setReadOnly: function(readOnly) {
103413         var me = this,
103414             textareaEl = me.textareaEl,
103415             iframeEl = me.iframeEl,
103416             body;
103417
103418         me.readOnly = readOnly;
103419
103420         if (textareaEl) {
103421             textareaEl.dom.readOnly = readOnly;
103422         }
103423
103424         if (me.initialized) {
103425             body = me.getEditorBody();
103426             if (Ext.isIE) {
103427                 // Hide the iframe while setting contentEditable so it doesn't grab focus
103428                 iframeEl.setDisplayed(false);
103429                 body.contentEditable = !readOnly;
103430                 iframeEl.setDisplayed(true);
103431             } else {
103432                 me.setDesignMode(!readOnly);
103433             }
103434             if (body) {
103435                 body.style.cursor = readOnly ? 'default' : 'text';
103436             }
103437             me.disableItems(readOnly);
103438         }
103439     },
103440
103441     /**
103442      * Protected method that will not generally be called directly. It
103443      * is called when the editor initializes the iframe with HTML contents. Override this method if you
103444      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
103445      *
103446      * Note: IE8-Standards has unwanted scroller behavior, so the default meta tag forces IE7 compatibility.
103447      * Also note that forcing IE7 mode works when the page is loaded normally, but if you are using IE's Web
103448      * Developer Tools to manually set the document mode, that will take precedence and override what this
103449      * code sets by default. This can be confusing when developing, but is not a user-facing issue.
103450      */
103451     getDocMarkup: function() {
103452         var me = this,
103453             h = me.iframeEl.getHeight() - me.iframePad * 2;
103454         return Ext.String.format('<html><head><style type="text/css">body{border:0;margin:0;padding:{0}px;height:{1}px;cursor:text}</style></head><body></body></html>', me.iframePad, h);
103455     },
103456
103457     // private
103458     getEditorBody: function() {
103459         var doc = this.getDoc();
103460         return doc.body || doc.documentElement;
103461     },
103462
103463     // private
103464     getDoc: function() {
103465         return (!Ext.isIE && this.iframeEl.dom.contentDocument) || this.getWin().document;
103466     },
103467
103468     // private
103469     getWin: function() {
103470         return Ext.isIE ? this.iframeEl.dom.contentWindow : window.frames[this.iframeEl.dom.name];
103471     },
103472
103473     // private
103474     onRender: function() {
103475         var me = this,
103476             renderSelectors = me.renderSelectors;
103477
103478         Ext.applyIf(renderSelectors, me.getLabelableSelectors());
103479
103480         Ext.applyIf(renderSelectors, {
103481             toolbarWrap: 'div.' + Ext.baseCSSPrefix + 'html-editor-tb',
103482             iframeEl: 'iframe',
103483             textareaEl: 'textarea'
103484         });
103485
103486         me.callParent(arguments);
103487
103488         me.textareaEl.dom.value = me.value || '';
103489
103490         // Start polling for when the iframe document is ready to be manipulated
103491         me.monitorTask = Ext.TaskManager.start({
103492             run: me.checkDesignMode,
103493             scope: me,
103494             interval:100
103495         });
103496
103497         me.createToolbar(me);
103498         me.disableItems(true);
103499     },
103500
103501     initRenderTpl: function() {
103502         var me = this;
103503         if (!me.hasOwnProperty('renderTpl')) {
103504             me.renderTpl = me.getTpl('labelableRenderTpl');
103505         }
103506         return me.callParent();
103507     },
103508
103509     initRenderData: function() {
103510         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
103511     },
103512
103513     getSubTplData: function() {
103514         var cssPrefix = Ext.baseCSSPrefix;
103515         return {
103516             toolbarWrapCls: cssPrefix + 'html-editor-tb',
103517             textareaCls: cssPrefix + 'hidden',
103518             iframeName: Ext.id(),
103519             iframeSrc: Ext.SSL_SECURE_URL,
103520             size: 'height:100px;'
103521         };
103522     },
103523
103524     getSubTplMarkup: function() {
103525         return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
103526     },
103527
103528     getBodyNaturalWidth: function() {
103529         return 565;
103530     },
103531
103532     initFrameDoc: function() {
103533         var me = this,
103534             doc, task;
103535
103536         Ext.TaskManager.stop(me.monitorTask);
103537
103538         doc = me.getDoc();
103539         me.win = me.getWin();
103540
103541         doc.open();
103542         doc.write(me.getDocMarkup());
103543         doc.close();
103544
103545         task = { // must defer to wait for browser to be ready
103546             run: function() {
103547                 var doc = me.getDoc();
103548                 if (doc.body || doc.readyState === 'complete') {
103549                     Ext.TaskManager.stop(task);
103550                     me.setDesignMode(true);
103551                     Ext.defer(me.initEditor, 10, me);
103552                 }
103553             },
103554             interval : 10,
103555             duration:10000,
103556             scope: me
103557         };
103558         Ext.TaskManager.start(task);
103559     },
103560
103561     checkDesignMode: function() {
103562         var me = this,
103563             doc = me.getDoc();
103564         if (doc && (!doc.editorInitialized || me.getDesignMode() !== 'on')) {
103565             me.initFrameDoc();
103566         }
103567     },
103568
103569     /* private
103570      * set current design mode. To enable, mode can be true or 'on', off otherwise
103571      */
103572     setDesignMode: function(mode) {
103573         var me = this,
103574             doc = me.getDoc();
103575         if (doc) {
103576             if (me.readOnly) {
103577                 mode = false;
103578             }
103579             doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off';
103580         }
103581     },
103582
103583     // private
103584     getDesignMode: function() {
103585         var doc = this.getDoc();
103586         return !doc ? '' : String(doc.designMode).toLowerCase();
103587     },
103588
103589     disableItems: function(disabled) {
103590         this.getToolbar().items.each(function(item){
103591             if(item.getItemId() !== 'sourceedit'){
103592                 item.setDisabled(disabled);
103593             }
103594         });
103595     },
103596
103597     /**
103598      * Toggles the editor between standard and source edit mode.
103599      * @param {Boolean} sourceEditMode (optional) True for source edit, false for standard
103600      */
103601     toggleSourceEdit: function(sourceEditMode) {
103602         var me = this,
103603             iframe = me.iframeEl,
103604             textarea = me.textareaEl,
103605             hiddenCls = Ext.baseCSSPrefix + 'hidden',
103606             btn = me.getToolbar().getComponent('sourceedit');
103607
103608         if (!Ext.isBoolean(sourceEditMode)) {
103609             sourceEditMode = !me.sourceEditMode;
103610         }
103611         me.sourceEditMode = sourceEditMode;
103612
103613         if (btn.pressed !== sourceEditMode) {
103614             btn.toggle(sourceEditMode);
103615         }
103616         if (sourceEditMode) {
103617             me.disableItems(true);
103618             me.syncValue();
103619             iframe.addCls(hiddenCls);
103620             textarea.removeCls(hiddenCls);
103621             textarea.dom.removeAttribute('tabIndex');
103622             textarea.focus();
103623         }
103624         else {
103625             if (me.initialized) {
103626                 me.disableItems(me.readOnly);
103627             }
103628             me.pushValue();
103629             iframe.removeCls(hiddenCls);
103630             textarea.addCls(hiddenCls);
103631             textarea.dom.setAttribute('tabIndex', -1);
103632             me.deferFocus();
103633         }
103634         me.fireEvent('editmodechange', me, sourceEditMode);
103635         me.doComponentLayout();
103636     },
103637
103638     // private used internally
103639     createLink : function() {
103640         var url = prompt(this.createLinkText, this.defaultLinkValue);
103641         if (url && url !== 'http:/'+'/') {
103642             this.relayCmd('createlink', url);
103643         }
103644     },
103645
103646     clearInvalid: Ext.emptyFn,
103647
103648     // docs inherit from Field
103649     setValue: function(value) {
103650         var me = this,
103651             textarea = me.textareaEl;
103652         me.mixins.field.setValue.call(me, value);
103653         if (value === null || value === undefined) {
103654             value = '';
103655         }
103656         if (textarea) {
103657             textarea.dom.value = value;
103658         }
103659         me.pushValue();
103660         return this;
103661     },
103662
103663     /**
103664      * Protected method that will not generally be called directly. If you need/want
103665      * custom HTML cleanup, this is the method you should override.
103666      * @param {String} html The HTML to be cleaned
103667      * @return {String} The cleaned HTML
103668      */
103669     cleanHtml: function(html) {
103670         html = String(html);
103671         if (Ext.isWebKit) { // strip safari nonsense
103672             html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
103673         }
103674
103675         /*
103676          * Neat little hack. Strips out all the non-digit characters from the default
103677          * value and compares it to the character code of the first character in the string
103678          * because it can cause encoding issues when posted to the server.
103679          */
103680         if (html.charCodeAt(0) === this.defaultValue.replace(/\D/g, '')) {
103681             html = html.substring(1);
103682         }
103683         return html;
103684     },
103685
103686     /**
103687      * @protected method that will not generally be called directly. Syncs the contents
103688      * of the editor iframe with the textarea.
103689      */
103690     syncValue : function(){
103691         var me = this,
103692             body, html, bodyStyle, match;
103693         if (me.initialized) {
103694             body = me.getEditorBody();
103695             html = body.innerHTML;
103696             if (Ext.isWebKit) {
103697                 bodyStyle = body.getAttribute('style'); // Safari puts text-align styles on the body element!
103698                 match = bodyStyle.match(/text-align:(.*?);/i);
103699                 if (match && match[1]) {
103700                     html = '<div style="' + match[0] + '">' + html + '</div>';
103701                 }
103702             }
103703             html = me.cleanHtml(html);
103704             if (me.fireEvent('beforesync', me, html) !== false) {
103705                 me.textareaEl.dom.value = html;
103706                 me.fireEvent('sync', me, html);
103707             }
103708         }
103709     },
103710
103711     //docs inherit from Field
103712     getValue : function() {
103713         var me = this,
103714             value;
103715         if (!me.sourceEditMode) {
103716             me.syncValue();
103717         }
103718         value = me.rendered ? me.textareaEl.dom.value : me.value;
103719         me.value = value;
103720         return value;
103721     },
103722
103723     /**
103724      * @protected method that will not generally be called directly. Pushes the value of the textarea
103725      * into the iframe editor.
103726      */
103727     pushValue: function() {
103728         var me = this,
103729             v;
103730         if(me.initialized){
103731             v = me.textareaEl.dom.value || '';
103732             if (!me.activated && v.length < 1) {
103733                 v = me.defaultValue;
103734             }
103735             if (me.fireEvent('beforepush', me, v) !== false) {
103736                 me.getEditorBody().innerHTML = v;
103737                 if (Ext.isGecko) {
103738                     // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
103739                     me.setDesignMode(false);  //toggle off first
103740                     me.setDesignMode(true);
103741                 }
103742                 me.fireEvent('push', me, v);
103743             }
103744         }
103745     },
103746
103747     // private
103748     deferFocus : function(){
103749          this.focus(false, true);
103750     },
103751
103752     getFocusEl: function() {
103753         var me = this,
103754             win = me.win;
103755         return win && !me.sourceEditMode ? win : me.textareaEl;
103756     },
103757
103758     // private
103759     initEditor : function(){
103760         //Destroying the component during/before initEditor can cause issues.
103761         try {
103762             var me = this,
103763                 dbody = me.getEditorBody(),
103764                 ss = me.textareaEl.getStyles('font-size', 'font-family', 'background-image', 'background-repeat', 'background-color', 'color'),
103765                 doc,
103766                 fn;
103767
103768             ss['background-attachment'] = 'fixed'; // w3c
103769             dbody.bgProperties = 'fixed'; // ie
103770
103771             Ext.core.DomHelper.applyStyles(dbody, ss);
103772
103773             doc = me.getDoc();
103774
103775             if (doc) {
103776                 try {
103777                     Ext.EventManager.removeAll(doc);
103778                 } catch(e) {}
103779             }
103780
103781             /*
103782              * We need to use createDelegate here, because when using buffer, the delayed task is added
103783              * as a property to the function. When the listener is removed, the task is deleted from the function.
103784              * Since onEditorEvent is shared on the prototype, if we have multiple html editors, the first time one of the editors
103785              * is destroyed, it causes the fn to be deleted from the prototype, which causes errors. Essentially, we're just anonymizing the function.
103786              */
103787             fn = Ext.Function.bind(me.onEditorEvent, me);
103788             Ext.EventManager.on(doc, {
103789                 mousedown: fn,
103790                 dblclick: fn,
103791                 click: fn,
103792                 keyup: fn,
103793                 buffer:100
103794             });
103795
103796             // These events need to be relayed from the inner document (where they stop
103797             // bubbling) up to the outer document. This has to be done at the DOM level so
103798             // the event reaches listeners on elements like the document body. The effected
103799             // mechanisms that depend on this bubbling behavior are listed to the right
103800             // of the event.
103801             fn = me.onRelayedEvent;
103802             Ext.EventManager.on(doc, {
103803                 mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
103804                 mousemove: fn, // window resize drag detection
103805                 mouseup: fn,   // window resize termination
103806                 click: fn,     // not sure, but just to be safe
103807                 dblclick: fn,  // not sure again
103808                 scope: me
103809             });
103810
103811             if (Ext.isGecko) {
103812                 Ext.EventManager.on(doc, 'keypress', me.applyCommand, me);
103813             }
103814             if (me.fixKeys) {
103815                 Ext.EventManager.on(doc, 'keydown', me.fixKeys, me);
103816             }
103817
103818             // We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
103819             Ext.EventManager.on(window, 'unload', me.beforeDestroy, me);
103820             doc.editorInitialized = true;
103821
103822             me.initialized = true;
103823             me.pushValue();
103824             me.setReadOnly(me.readOnly);
103825             me.fireEvent('initialize', me);
103826         } catch(ex) {
103827             // ignore (why?)
103828         }
103829     },
103830
103831     // private
103832     beforeDestroy : function(){
103833         var me = this,
103834             monitorTask = me.monitorTask,
103835             doc, prop;
103836
103837         if (monitorTask) {
103838             Ext.TaskManager.stop(monitorTask);
103839         }
103840         if (me.rendered) {
103841             try {
103842                 doc = me.getDoc();
103843                 if (doc) {
103844                     Ext.EventManager.removeAll(doc);
103845                     for (prop in doc) {
103846                         if (doc.hasOwnProperty(prop)) {
103847                             delete doc[prop];
103848                         }
103849                     }
103850                 }
103851             } catch(e) {
103852                 // ignore (why?)
103853             }
103854             Ext.destroyMembers('tb', 'toolbarWrap', 'iframeEl', 'textareaEl');
103855         }
103856         me.callParent();
103857     },
103858
103859     // private
103860     onRelayedEvent: function (event) {
103861         // relay event from the iframe's document to the document that owns the iframe...
103862
103863         var iframeEl = this.iframeEl,
103864             iframeXY = iframeEl.getXY(),
103865             eventXY = event.getXY();
103866
103867         // the event from the inner document has XY relative to that document's origin,
103868         // so adjust it to use the origin of the iframe in the outer document:
103869         event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]];
103870
103871         event.injectEvent(iframeEl); // blame the iframe for the event...
103872
103873         event.xy = eventXY; // restore the original XY (just for safety)
103874     },
103875
103876     // private
103877     onFirstFocus : function(){
103878         var me = this,
103879             selection, range;
103880         me.activated = true;
103881         me.disableItems(me.readOnly);
103882         if (Ext.isGecko) { // prevent silly gecko errors
103883             me.win.focus();
103884             selection = me.win.getSelection();
103885             if (!selection.focusNode || selection.focusNode.nodeType !== 3) {
103886                 range = selection.getRangeAt(0);
103887                 range.selectNodeContents(me.getEditorBody());
103888                 range.collapse(true);
103889                 me.deferFocus();
103890             }
103891             try {
103892                 me.execCmd('useCSS', true);
103893                 me.execCmd('styleWithCSS', false);
103894             } catch(e) {
103895                 // ignore (why?)
103896             }
103897         }
103898         me.fireEvent('activate', me);
103899     },
103900
103901     // private
103902     adjustFont: function(btn) {
103903         var adjust = btn.getItemId() === 'increasefontsize' ? 1 : -1,
103904             size = this.getDoc().queryCommandValue('FontSize') || '2',
103905             isPxSize = Ext.isString(size) && size.indexOf('px') !== -1,
103906             isSafari;
103907         size = parseInt(size, 10);
103908         if (isPxSize) {
103909             // Safari 3 values
103910             // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
103911             if (size <= 10) {
103912                 size = 1 + adjust;
103913             }
103914             else if (size <= 13) {
103915                 size = 2 + adjust;
103916             }
103917             else if (size <= 16) {
103918                 size = 3 + adjust;
103919             }
103920             else if (size <= 18) {
103921                 size = 4 + adjust;
103922             }
103923             else if (size <= 24) {
103924                 size = 5 + adjust;
103925             }
103926             else {
103927                 size = 6 + adjust;
103928             }
103929             size = Ext.Number.constrain(size, 1, 6);
103930         } else {
103931             isSafari = Ext.isSafari;
103932             if (isSafari) { // safari
103933                 adjust *= 2;
103934             }
103935             size = Math.max(1, size + adjust) + (isSafari ? 'px' : 0);
103936         }
103937         this.execCmd('FontSize', size);
103938     },
103939
103940     // private
103941     onEditorEvent: function(e) {
103942         this.updateToolbar();
103943     },
103944
103945     /**
103946      * Protected method that will not generally be called directly. It triggers
103947      * a toolbar update by reading the markup state of the current selection in the editor.
103948      */
103949     updateToolbar: function() {
103950         var me = this,
103951             btns, doc, name, fontSelect;
103952
103953         if (me.readOnly) {
103954             return;
103955         }
103956
103957         if (!me.activated) {
103958             me.onFirstFocus();
103959             return;
103960         }
103961
103962         btns = me.getToolbar().items.map;
103963         doc = me.getDoc();
103964
103965         if (me.enableFont && !Ext.isSafari2) {
103966             name = (doc.queryCommandValue('FontName') || me.defaultFont).toLowerCase();
103967             fontSelect = me.fontSelect.dom;
103968             if (name !== fontSelect.value) {
103969                 fontSelect.value = name;
103970             }
103971         }
103972
103973         function updateButtons() {
103974             Ext.Array.forEach(Ext.Array.toArray(arguments), function(name) {
103975                 btns[name].toggle(doc.queryCommandState(name));
103976             });
103977         }
103978         if(me.enableFormat){
103979             updateButtons('bold', 'italic', 'underline');
103980         }
103981         if(me.enableAlignments){
103982             updateButtons('justifyleft', 'justifycenter', 'justifyright');
103983         }
103984         if(!Ext.isSafari2 && me.enableLists){
103985             updateButtons('insertorderedlist', 'insertunorderedlist');
103986         }
103987
103988         Ext.menu.Manager.hideAll();
103989
103990         me.syncValue();
103991     },
103992
103993     // private
103994     relayBtnCmd: function(btn) {
103995         this.relayCmd(btn.getItemId());
103996     },
103997
103998     /**
103999      * Executes a Midas editor command on the editor document and performs necessary focus and
104000      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
104001      * @param {String} cmd The Midas command
104002      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
104003      */
104004     relayCmd: function(cmd, value) {
104005         Ext.defer(function() {
104006             var me = this;
104007             me.focus();
104008             me.execCmd(cmd, value);
104009             me.updateToolbar();
104010         }, 10, this);
104011     },
104012
104013     /**
104014      * Executes a Midas editor command directly on the editor document.
104015      * For visual commands, you should use {@link #relayCmd} instead.
104016      * <b>This should only be called after the editor is initialized.</b>
104017      * @param {String} cmd The Midas command
104018      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
104019      */
104020     execCmd : function(cmd, value){
104021         var me = this,
104022             doc = me.getDoc(),
104023             undef;
104024         doc.execCommand(cmd, false, value === undef ? null : value);
104025         me.syncValue();
104026     },
104027
104028     // private
104029     applyCommand : function(e){
104030         if (e.ctrlKey) {
104031             var me = this,
104032                 c = e.getCharCode(), cmd;
104033             if (c > 0) {
104034                 c = String.fromCharCode(c);
104035                 switch (c) {
104036                     case 'b':
104037                         cmd = 'bold';
104038                     break;
104039                     case 'i':
104040                         cmd = 'italic';
104041                     break;
104042                     case 'u':
104043                         cmd = 'underline';
104044                     break;
104045                 }
104046                 if (cmd) {
104047                     me.win.focus();
104048                     me.execCmd(cmd);
104049                     me.deferFocus();
104050                     e.preventDefault();
104051                 }
104052             }
104053         }
104054     },
104055
104056     /**
104057      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
104058      * to insert text.
104059      * @param {String} text
104060      */
104061     insertAtCursor : function(text){
104062         var me = this,
104063             range;
104064
104065         if (me.activated) {
104066             me.win.focus();
104067             if (Ext.isIE) {
104068                 range = me.getDoc().selection.createRange();
104069                 if (range) {
104070                     range.pasteHTML(text);
104071                     me.syncValue();
104072                     me.deferFocus();
104073                 }
104074             }else{
104075                 me.execCmd('InsertHTML', text);
104076                 me.deferFocus();
104077             }
104078         }
104079     },
104080
104081     // private
104082     fixKeys: function() { // load time branching for fastest keydown performance
104083         if (Ext.isIE) {
104084             return function(e){
104085                 var me = this,
104086                     k = e.getKey(),
104087                     doc = me.getDoc(),
104088                     range, target;
104089                 if (k === e.TAB) {
104090                     e.stopEvent();
104091                     range = doc.selection.createRange();
104092                     if(range){
104093                         range.collapse(true);
104094                         range.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
104095                         me.deferFocus();
104096                     }
104097                 }
104098                 else if (k === e.ENTER) {
104099                     range = doc.selection.createRange();
104100                     if (range) {
104101                         target = range.parentElement();
104102                         if(!target || target.tagName.toLowerCase() !== 'li'){
104103                             e.stopEvent();
104104                             range.pasteHTML('<br />');
104105                             range.collapse(false);
104106                             range.select();
104107                         }
104108                     }
104109                 }
104110             };
104111         }
104112
104113         if (Ext.isOpera) {
104114             return function(e){
104115                 var me = this;
104116                 if (e.getKey() === e.TAB) {
104117                     e.stopEvent();
104118                     me.win.focus();
104119                     me.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');
104120                     me.deferFocus();
104121                 }
104122             };
104123         }
104124
104125         if (Ext.isWebKit) {
104126             return function(e){
104127                 var me = this,
104128                     k = e.getKey();
104129                 if (k === e.TAB) {
104130                     e.stopEvent();
104131                     me.execCmd('InsertText','\t');
104132                     me.deferFocus();
104133                 }
104134                 else if (k === e.ENTER) {
104135                     e.stopEvent();
104136                     me.execCmd('InsertHtml','<br /><br />');
104137                     me.deferFocus();
104138                 }
104139             };
104140         }
104141
104142         return null; // not needed, so null
104143     }(),
104144
104145     /**
104146      * Returns the editor's toolbar. <b>This is only available after the editor has been rendered.</b>
104147      * @return {Ext.toolbar.Toolbar}
104148      */
104149     getToolbar : function(){
104150         return this.toolbar;
104151     },
104152
104153     /**
104154      * Object collection of toolbar tooltips for the buttons in the editor. The key
104155      * is the command id associated with that button and the value is a valid QuickTips object.
104156      * For example:
104157 <pre><code>
104158 {
104159     bold : {
104160         title: 'Bold (Ctrl+B)',
104161         text: 'Make the selected text bold.',
104162         cls: 'x-html-editor-tip'
104163     },
104164     italic : {
104165         title: 'Italic (Ctrl+I)',
104166         text: 'Make the selected text italic.',
104167         cls: 'x-html-editor-tip'
104168     },
104169     ...
104170 </code></pre>
104171     * @type Object
104172      */
104173     buttonTips : {
104174         bold : {
104175             title: 'Bold (Ctrl+B)',
104176             text: 'Make the selected text bold.',
104177             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104178         },
104179         italic : {
104180             title: 'Italic (Ctrl+I)',
104181             text: 'Make the selected text italic.',
104182             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104183         },
104184         underline : {
104185             title: 'Underline (Ctrl+U)',
104186             text: 'Underline the selected text.',
104187             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104188         },
104189         increasefontsize : {
104190             title: 'Grow Text',
104191             text: 'Increase the font size.',
104192             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104193         },
104194         decreasefontsize : {
104195             title: 'Shrink Text',
104196             text: 'Decrease the font size.',
104197             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104198         },
104199         backcolor : {
104200             title: 'Text Highlight Color',
104201             text: 'Change the background color of the selected text.',
104202             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104203         },
104204         forecolor : {
104205             title: 'Font Color',
104206             text: 'Change the color of the selected text.',
104207             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104208         },
104209         justifyleft : {
104210             title: 'Align Text Left',
104211             text: 'Align text to the left.',
104212             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104213         },
104214         justifycenter : {
104215             title: 'Center Text',
104216             text: 'Center text in the editor.',
104217             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104218         },
104219         justifyright : {
104220             title: 'Align Text Right',
104221             text: 'Align text to the right.',
104222             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104223         },
104224         insertunorderedlist : {
104225             title: 'Bullet List',
104226             text: 'Start a bulleted list.',
104227             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104228         },
104229         insertorderedlist : {
104230             title: 'Numbered List',
104231             text: 'Start a numbered list.',
104232             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104233         },
104234         createlink : {
104235             title: 'Hyperlink',
104236             text: 'Make the selected text a hyperlink.',
104237             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104238         },
104239         sourceedit : {
104240             title: 'Source Edit',
104241             text: 'Switch to source editing mode.',
104242             cls: Ext.baseCSSPrefix + 'html-editor-tip'
104243         }
104244     }
104245
104246     // hide stuff that is not compatible
104247     /**
104248      * @event blur
104249      * @hide
104250      */
104251     /**
104252      * @event change
104253      * @hide
104254      */
104255     /**
104256      * @event focus
104257      * @hide
104258      */
104259     /**
104260      * @event specialkey
104261      * @hide
104262      */
104263     /**
104264      * @cfg {String} fieldCls @hide
104265      */
104266     /**
104267      * @cfg {String} focusCls @hide
104268      */
104269     /**
104270      * @cfg {String} autoCreate @hide
104271      */
104272     /**
104273      * @cfg {String} inputType @hide
104274      */
104275     /**
104276      * @cfg {String} invalidCls @hide
104277      */
104278     /**
104279      * @cfg {String} invalidText @hide
104280      */
104281     /**
104282      * @cfg {String} msgFx @hide
104283      */
104284     /**
104285      * @cfg {Boolean} allowDomMove  @hide
104286      */
104287     /**
104288      * @cfg {String} applyTo @hide
104289      */
104290     /**
104291      * @cfg {String} readOnly  @hide
104292      */
104293     /**
104294      * @cfg {String} tabIndex  @hide
104295      */
104296     /**
104297      * @method validate
104298      * @hide
104299      */
104300 });
104301
104302 /**
104303  * @class Ext.form.field.Radio
104304  * @extends Ext.form.field.Checkbox
104305
104306 Single radio field. Similar to checkbox, but automatically handles making sure only one radio is checked
104307 at a time within a group of radios with the same name.
104308
104309 __Labeling:__
104310 In addition to the {@link Ext.form.Labelable standard field labeling options}, radio buttons
104311 may be given an optional {@link #boxLabel} which will be displayed immediately to the right of the input. Also
104312 see {@link Ext.form.RadioGroup} for a convenient method of grouping related radio buttons.
104313
104314 __Values:__
104315 The main value of a Radio field is a boolean, indicating whether or not the radio is checked.
104316
104317 The following values will check the radio:
104318 * `true`
104319 * `'true'`
104320 * `'1'`
104321 * `'on'`
104322
104323 Any other value will uncheck it.
104324
104325 In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be sent
104326 as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set this
104327 value if you have multiple radio buttons with the same {@link #name}, as is almost always the case.
104328 {@img Ext.form.Radio/Ext.form.Radio.png Ext.form.Radio component}
104329 __Example usage:__
104330
104331     Ext.create('Ext.form.Panel', {
104332         title      : 'Order Form',
104333         width      : 300,
104334         bodyPadding: 10,
104335         renderTo   : Ext.getBody(),
104336         items: [
104337             {
104338                 xtype      : 'fieldcontainer',
104339                 fieldLabel : 'Size',
104340                 defaultType: 'radiofield',
104341                 defaults: {
104342                     flex: 1
104343                 },
104344                 layout: 'hbox',
104345                 items: [
104346                     {
104347                         boxLabel  : 'M',
104348                         name      : 'size',
104349                         inputValue: 'm',
104350                         id        : 'radio1'
104351                     }, {
104352                         boxLabel  : 'L',
104353                         name      : 'size',
104354                         inputValue: 'l',
104355                         id        : 'radio2'
104356                     }, {
104357                         boxLabel  : 'XL',
104358                         name      : 'size',
104359                         inputValue: 'xl',
104360                         id        : 'radio3'
104361                     }
104362                 ]
104363             },
104364             {
104365                 xtype      : 'fieldcontainer',
104366                 fieldLabel : 'Color',
104367                 defaultType: 'radiofield',
104368                 defaults: {
104369                     flex: 1
104370                 },
104371                 layout: 'hbox',
104372                 items: [
104373                     {
104374                         boxLabel  : 'Blue',
104375                         name      : 'color',
104376                         inputValue: 'blue',
104377                         id        : 'radio4'
104378                     }, {
104379                         boxLabel  : 'Grey',
104380                         name      : 'color',
104381                         inputValue: 'grey',
104382                         id        : 'radio5'
104383                     }, {
104384                         boxLabel  : 'Black',
104385                         name      : 'color',
104386                         inputValue: 'black',
104387                         id        : 'radio6'
104388                     }
104389                 ]
104390             }
104391         ],
104392         bbar: [
104393             {
104394                 text: 'Smaller Size',
104395                 handler: function() {
104396                     var radio1 = Ext.getCmp('radio1'),
104397                         radio2 = Ext.getCmp('radio2'),
104398                         radio3 = Ext.getCmp('radio3');
104399
104400                     //if L is selected, change to M
104401                     if (radio2.getValue()) {
104402                         radio1.setValue(true);
104403                         return;
104404                     }
104405
104406                     //if XL is selected, change to L
104407                     if (radio3.getValue()) {
104408                         radio2.setValue(true);
104409                         return;
104410                     }
104411
104412                     //if nothing is set, set size to S
104413                     radio1.setValue(true);
104414                 }
104415             },
104416             {
104417                 text: 'Larger Size',
104418                 handler: function() {
104419                     var radio1 = Ext.getCmp('radio1'),
104420                         radio2 = Ext.getCmp('radio2'),
104421                         radio3 = Ext.getCmp('radio3');
104422
104423                     //if M is selected, change to L
104424                     if (radio1.getValue()) {
104425                         radio2.setValue(true);
104426                         return;
104427                     }
104428
104429                     //if L is selected, change to XL
104430                     if (radio2.getValue()) {
104431                         radio3.setValue(true);
104432                         return;
104433                     }
104434
104435                     //if nothing is set, set size to XL
104436                     radio3.setValue(true);
104437                 }
104438             },
104439             '-',
104440             {
104441                 text: 'Select color',
104442                 menu: {
104443                     indent: false,
104444                     items: [
104445                         {
104446                             text: 'Blue',
104447                             handler: function() {
104448                                 var radio = Ext.getCmp('radio4');
104449                                 radio.setValue(true);
104450                             }
104451                         },
104452                         {
104453                             text: 'Grey',
104454                             handler: function() {
104455                                 var radio = Ext.getCmp('radio5');
104456                                 radio.setValue(true);
104457                             }
104458                         },
104459                         {
104460                             text: 'Black',
104461                             handler: function() {
104462                                 var radio = Ext.getCmp('radio6');
104463                                 radio.setValue(true);
104464                             }
104465                         }
104466                     ]
104467                 }
104468             }
104469         ]
104470     });
104471
104472
104473  * @constructor
104474  * Creates a new Radio
104475  * @param {Object} config Configuration options
104476  * @xtype radio
104477  * @docauthor Robert Dougan <rob@sencha.com>
104478  * @markdown
104479  */
104480 Ext.define('Ext.form.field.Radio', {
104481     extend:'Ext.form.field.Checkbox',
104482     alias: ['widget.radiofield', 'widget.radio'],
104483     alternateClassName: 'Ext.form.Radio',
104484     requires: ['Ext.form.RadioManager'],
104485
104486     isRadio: true,
104487
104488     /**
104489      * @cfg {String} uncheckedValue @hide
104490      */
104491
104492     // private
104493     inputType: 'radio',
104494     ariaRole: 'radio',
104495
104496     /**
104497      * If this radio is part of a group, it will return the selected value
104498      * @return {String}
104499      */
104500     getGroupValue: function() {
104501         var selected = this.getManager().getChecked(this.name);
104502         return selected ? selected.inputValue : null;
104503     },
104504
104505     /**
104506      * @private Handle click on the radio button
104507      */
104508     onBoxClick: function(e) {
104509         var me = this;
104510         if (!me.disabled && !me.readOnly) {
104511             this.setValue(true);
104512         }
104513     },
104514
104515     /**
104516      * Sets either the checked/unchecked status of this Radio, or, if a string value
104517      * is passed, checks a sibling Radio of the same name whose value is the value specified.
104518      * @param value {String/Boolean} Checked value, or the value of the sibling radio button to check.
104519      * @return {Ext.form.field.Radio} this
104520      */
104521     setValue: function(v) {
104522         var me = this,
104523             active;
104524
104525         if (Ext.isBoolean(v)) {
104526             me.callParent(arguments);
104527         } else {
104528             active = me.getManager().getWithValue(me.name, v).getAt(0);
104529             if (active) {
104530                 active.setValue(true);
104531             }
104532         }
104533         return me;
104534     },
104535
104536     /**
104537      * Returns the submit value for the checkbox which can be used when submitting forms.
104538      * @return {Boolean/null} True if checked, null if not.
104539      */
104540     getSubmitValue: function() {
104541         return this.checked ? this.inputValue : null;
104542     },
104543
104544     getModelData: function() {
104545         return this.getSubmitData();
104546     },
104547
104548     // inherit docs
104549     onChange: function(newVal, oldVal) {
104550         var me = this;
104551         me.callParent(arguments);
104552
104553         if (newVal) {
104554             this.getManager().getByName(me.name).each(function(item){
104555                 if (item !== me) {
104556                     item.setValue(false);
104557                 }
104558             }, me);
104559         }
104560     },
104561
104562     // inherit docs
104563     beforeDestroy: function(){
104564         this.callParent();
104565         this.getManager().removeAtKey(this.id);
104566     },
104567
104568     // inherit docs
104569     getManager: function() {
104570         return Ext.form.RadioManager;
104571     }
104572 });
104573
104574 /**
104575  * @class Ext.picker.Time
104576  * @extends Ext.view.BoundList
104577  * <p>A time picker which provides a list of times from which to choose. This is used by the
104578  * {@link Ext.form.field.Time} class to allow browsing and selection of valid times, but could also be used
104579  * with other components.</p>
104580  * <p>By default, all times starting at midnight and incrementing every 15 minutes will be presented.
104581  * This list of available times can be controlled using the {@link #minValue}, {@link #maxValue}, and
104582  * {@link #increment} configuration properties. The format of the times presented in the list can be
104583  * customized with the {@link #format} config.</p>
104584  * <p>To handle when the user selects a time from the list, you can subscribe to the {@link #selectionchange}
104585  * event.</p>
104586  *
104587  * {@img Ext.picker.Time/Ext.picker.Time.png Ext.picker.Time component}
104588  *
104589  * ## Code
104590      new Ext.create('Ext.picker.Time', {
104591         width: 60,
104592         minValue: Ext.Date.parse('04:30:00 AM', 'h:i:s A'),
104593         maxValue: Ext.Date.parse('08:00:00 AM', 'h:i:s A'),
104594         renderTo: Ext.getBody()
104595     });
104596  *
104597  * @constructor
104598  * Create a new TimePicker
104599  * @param {Object} config The config object
104600  *
104601  * @xtype timepicker
104602  */
104603 Ext.define('Ext.picker.Time', {
104604     extend: 'Ext.view.BoundList',
104605     alias: 'widget.timepicker',
104606     requires: ['Ext.data.Store', 'Ext.Date'],
104607
104608     /**
104609      * @cfg {Date} minValue
104610      * The minimum time to be shown in the list of times. This must be a Date object (only the time fields
104611      * will be used); no parsing of String values will be done. Defaults to undefined.
104612      */
104613
104614     /**
104615      * @cfg {Date} maxValue
104616      * The maximum time to be shown in the list of times. This must be a Date object (only the time fields
104617      * will be used); no parsing of String values will be done. Defaults to undefined.
104618      */
104619
104620     /**
104621      * @cfg {Number} increment
104622      * The number of minutes between each time value in the list (defaults to 15).
104623      */
104624     increment: 15,
104625
104626     /**
104627      * @cfg {String} format
104628      * The default time format string which can be overriden for localization support. The format must be
104629      * valid according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time
104630      * format try 'H:i' instead.
104631      */
104632     format : "g:i A",
104633
104634     /**
104635      * @hide
104636      * The field in the implicitly-generated Model objects that gets displayed in the list. This is
104637      * an internal field name only and is not useful to change via config.
104638      */
104639     displayField: 'disp',
104640
104641     /**
104642      * @private
104643      * Year, month, and day that all times will be normalized into internally.
104644      */
104645     initDate: [2008,1,1],
104646
104647     componentCls: Ext.baseCSSPrefix + 'timepicker',
104648
104649     /**
104650      * @hide
104651      */
104652     loadingText: '',
104653
104654     initComponent: function() {
104655         var me = this,
104656             dateUtil = Ext.Date,
104657             clearTime = dateUtil.clearTime,
104658             initDate = me.initDate.join('/');
104659
104660         // Set up absolute min and max for the entire day
104661         me.absMin = clearTime(new Date(initDate));
104662         me.absMax = dateUtil.add(clearTime(new Date(initDate)), 'mi', (24 * 60) - 1);
104663
104664         me.store = me.createStore();
104665         me.updateList();
104666
104667         this.callParent();
104668     },
104669
104670     /**
104671      * Set the {@link #minValue} and update the list of available times. This must be a Date
104672      * object (only the time fields will be used); no parsing of String values will be done.
104673      * @param {Date} value
104674      */
104675     setMinValue: function(value) {
104676         this.minValue = value;
104677         this.updateList();
104678     },
104679
104680     /**
104681      * Set the {@link #maxValue} and update the list of available times. This must be a Date
104682      * object (only the time fields will be used); no parsing of String values will be done.
104683      * @param {Date} value
104684      */
104685     setMaxValue: function(value) {
104686         this.maxValue = value;
104687         this.updateList();
104688     },
104689
104690     /**
104691      * @private
104692      * Sets the year/month/day of the given Date object to the {@link #initDate}, so that only
104693      * the time fields are significant. This makes values suitable for time comparison.
104694      * @param {Date} date
104695      */
104696     normalizeDate: function(date) {
104697         var initDate = this.initDate;
104698         date.setFullYear(initDate[0], initDate[1] - 1, initDate[2]);
104699         return date;
104700     },
104701
104702     /**
104703      * Update the list of available times in the list to be constrained within the
104704      * {@link #minValue} and {@link #maxValue}.
104705      */
104706     updateList: function() {
104707         var me = this,
104708             min = me.normalizeDate(me.minValue || me.absMin),
104709             max = me.normalizeDate(me.maxValue || me.absMax);
104710
104711         me.store.filterBy(function(record) {
104712             var date = record.get('date');
104713             return date >= min && date <= max;
104714         });
104715     },
104716
104717     /**
104718      * @private
104719      * Creates the internal {@link Ext.data.Store} that contains the available times. The store
104720      * is loaded with all possible times, and it is later filtered to hide those times outside
104721      * the minValue/maxValue.
104722      */
104723     createStore: function() {
104724         var me = this,
104725             utilDate = Ext.Date,
104726             times = [],
104727             min = me.absMin,
104728             max = me.absMax;
104729
104730         while(min <= max){
104731             times.push({
104732                 disp: utilDate.dateFormat(min, me.format),
104733                 date: min
104734             });
104735             min = utilDate.add(min, 'mi', me.increment);
104736         }
104737
104738         return Ext.create('Ext.data.Store', {
104739             fields: ['disp', 'date'],
104740             data: times
104741         });
104742     }
104743
104744 });
104745
104746 /**
104747  * @class Ext.form.field.Time
104748  * @extends Ext.form.field.Picker
104749  * <p>Provides a time input field with a time dropdown and automatic time validation.</p>
104750  * <p>This field recognizes and uses JavaScript Date objects as its main {@link #value} type (only the time
104751  * portion of the date is used; the month/day/year are ignored). In addition, it recognizes string values which
104752  * are parsed according to the {@link #format} and/or {@link #altFormats} configs. These may be reconfigured
104753  * to use time formats appropriate for the user's locale.</p>
104754  * <p>The field may be limited to a certain range of times by using the {@link #minValue} and {@link #maxValue}
104755  * configs, and the interval between time options in the dropdown can be changed with the {@link #increment} config.</p>
104756  * {@img Ext.form.Time/Ext.form.Time.png Ext.form.Time component}
104757  * <p>Example usage:</p>
104758  * <pre><code>
104759 Ext.create('Ext.form.Panel', {
104760     title: 'Time Card',
104761     width: 300,
104762     bodyPadding: 10,
104763     renderTo: Ext.getBody(),        
104764     items: [{
104765         xtype: 'timefield',
104766         name: 'in',
104767         fieldLabel: 'Time In',
104768         minValue: '6:00 AM',
104769         maxValue: '8:00 PM',
104770         increment: 30,
104771         anchor: '100%'
104772     }, {
104773         xtype: 'timefield',
104774         name: 'out',
104775         fieldLabel: 'Time Out',
104776         minValue: '6:00 AM',
104777         maxValue: '8:00 PM',
104778         increment: 30,
104779         anchor: '100%'
104780    }]
104781 });
104782 </code></pre>
104783  * @constructor
104784  * Create a new Time field
104785  * @param {Object} config
104786  * @xtype timefield
104787  */
104788 Ext.define('Ext.form.field.Time', {
104789     extend:'Ext.form.field.Picker',
104790     alias: 'widget.timefield',
104791     requires: ['Ext.form.field.Date', 'Ext.picker.Time', 'Ext.view.BoundListKeyNav', 'Ext.Date'],
104792     alternateClassName: ['Ext.form.TimeField', 'Ext.form.Time'],
104793
104794     /**
104795      * @cfg {String} triggerCls
104796      * An additional CSS class used to style the trigger button.  The trigger will always get the
104797      * {@link #triggerBaseCls} by default and <tt>triggerCls</tt> will be <b>appended</b> if specified.
104798      * Defaults to <tt>'x-form-time-trigger'</tt> for the Time field trigger.
104799      */
104800     triggerCls: Ext.baseCSSPrefix + 'form-time-trigger',
104801
104802     /**
104803      * @cfg {Date/String} minValue
104804      * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string
104805      * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to undefined).
104806      */
104807
104808     /**
104809      * @cfg {Date/String} maxValue
104810      * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string
104811      * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to undefined).
104812      */
104813
104814     /**
104815      * @cfg {String} minText
104816      * The error text to display when the entered time is before {@link #minValue} (defaults to
104817      * 'The time in this field must be equal to or after {0}').
104818      */
104819     minText : "The time in this field must be equal to or after {0}",
104820
104821     /**
104822      * @cfg {String} maxText
104823      * The error text to display when the entered time is after {@link #maxValue} (defaults to
104824      * 'The time in this field must be equal to or before {0}').
104825      */
104826     maxText : "The time in this field must be equal to or before {0}",
104827
104828     /**
104829      * @cfg {String} invalidText
104830      * The error text to display when the time in the field is invalid (defaults to
104831      * '{value} is not a valid time').
104832      */
104833     invalidText : "{0} is not a valid time",
104834
104835     /**
104836      * @cfg {String} format
104837      * The default time format string which can be overriden for localization support.  The format must be
104838      * valid according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM').  For 24-hour time
104839      * format try 'H:i' instead.
104840      */
104841     format : "g:i A",
104842
104843     /**
104844      * @cfg {String} submitFormat The date format string which will be submitted to the server.
104845      * The format must be valid according to {@link Ext.Date#parse} (defaults to <tt>{@link #format}</tt>).
104846      */
104847
104848     /**
104849      * @cfg {String} altFormats
104850      * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
104851      * format (defaults to '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').
104852      */
104853     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",
104854
104855     /**
104856      * @cfg {Number} increment
104857      * The number of minutes between each time value in the list (defaults to 15).
104858      */
104859     increment: 15,
104860
104861     /**
104862      * @cfg {Number} pickerMaxHeight
104863      * The maximum height of the {@link Ext.picker.Time} dropdown. Defaults to 300.
104864      */
104865     pickerMaxHeight: 300,
104866
104867     /**
104868      * @cfg {Boolean} selectOnTab
104869      * Whether the Tab key should select the currently highlighted item. Defaults to <tt>true</tt>.
104870      */
104871     selectOnTab: true,
104872
104873     /**
104874      * @private
104875      * This is the date to use when generating time values in the absence of either minValue
104876      * or maxValue.  Using the current date causes DST issues on DST boundary dates, so this is an
104877      * arbitrary "safe" date that can be any date aside from DST boundary dates.
104878      */
104879     initDate: '1/1/2008',
104880     initDateFormat: 'j/n/Y',
104881
104882
104883     initComponent: function() {
104884         var me = this,
104885             min = me.minValue,
104886             max = me.maxValue;
104887         if (min) {
104888             me.setMinValue(min);
104889         }
104890         if (max) {
104891             me.setMaxValue(max);
104892         }
104893         this.callParent();
104894     },
104895
104896     initValue: function() {
104897         var me = this,
104898             value = me.value;
104899
104900         // If a String value was supplied, try to convert it to a proper Date object
104901         if (Ext.isString(value)) {
104902             me.value = me.rawToValue(value);
104903         }
104904
104905         me.callParent();
104906     },
104907
104908     /**
104909      * Replaces any existing {@link #minValue} with the new time and refreshes the picker's range.
104910      * @param {Date/String} value The minimum time that can be selected
104911      */
104912     setMinValue: function(value) {
104913         var me = this,
104914             picker = me.picker;
104915         me.setLimit(value, true);
104916         if (picker) {
104917             picker.setMinValue(me.minValue);
104918         }
104919     },
104920
104921     /**
104922      * Replaces any existing {@link #maxValue} with the new time and refreshes the picker's range.
104923      * @param {Date/String} value The maximum time that can be selected
104924      */
104925     setMaxValue: function(value) {
104926         var me = this,
104927             picker = me.picker;
104928         me.setLimit(value, false);
104929         if (picker) {
104930             picker.setMaxValue(me.maxValue);
104931         }
104932     },
104933
104934     /**
104935      * @private
104936      * Updates either the min or max value. Converts the user's value into a Date object whose
104937      * year/month/day is set to the {@link #initDate} so that only the time fields are significant.
104938      */
104939     setLimit: function(value, isMin) {
104940         var me = this,
104941             d, val;
104942         if (Ext.isString(value)) {
104943             d = me.parseDate(value);
104944         }
104945         else if (Ext.isDate(value)) {
104946             d = value;
104947         }
104948         if (d) {
104949             val = Ext.Date.clearTime(new Date(me.initDate));
104950             val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
104951             me[isMin ? 'minValue' : 'maxValue'] = val;
104952         }
104953     },
104954
104955     rawToValue: function(rawValue) {
104956         return this.parseDate(rawValue) || rawValue || null;
104957     },
104958
104959     valueToRaw: function(value) {
104960         return this.formatDate(this.parseDate(value));
104961     },
104962
104963     /**
104964      * Runs all of Time's validations and returns an array of any errors. Note that this first
104965      * runs Text's validations, so the returned array is an amalgamation of all field errors.
104966      * The additional validation checks are testing that the time format is valid, that the chosen
104967      * time is within the {@link #minValue} and {@link #maxValue} constraints set.
104968      * @param {Mixed} value The value to get errors for (defaults to the current field value)
104969      * @return {Array} All validation errors for this field
104970      */
104971     getErrors: function(value) {
104972         var me = this,
104973             format = Ext.String.format,
104974             errors = me.callParent(arguments),
104975             minValue = me.minValue,
104976             maxValue = me.maxValue,
104977             date;
104978
104979         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
104980
104981         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
104982              return errors;
104983         }
104984
104985         date = me.parseDate(value);
104986         if (!date) {
104987             errors.push(format(me.invalidText, value, me.format));
104988             return errors;
104989         }
104990
104991         if (minValue && date < minValue) {
104992             errors.push(format(me.minText, me.formatDate(minValue)));
104993         }
104994
104995         if (maxValue && date > maxValue) {
104996             errors.push(format(me.maxText, me.formatDate(maxValue)));
104997         }
104998
104999         return errors;
105000     },
105001
105002     formatDate: function() {
105003         return Ext.form.field.Date.prototype.formatDate.apply(this, arguments);
105004     },
105005
105006     /**
105007      * @private
105008      * Parses an input value into a valid Date object.
105009      * @param {String/Date} value
105010      */
105011     parseDate: function(value) {
105012         if (!value || Ext.isDate(value)) {
105013             return value;
105014         }
105015
105016         var me = this,
105017             val = me.safeParse(value, me.format),
105018             altFormats = me.altFormats,
105019             altFormatsArray = me.altFormatsArray,
105020             i = 0,
105021             len;
105022
105023         if (!val && altFormats) {
105024             altFormatsArray = altFormatsArray || altFormats.split('|');
105025             len = altFormatsArray.length;
105026             for (; i < len && !val; ++i) {
105027                 val = me.safeParse(value, altFormatsArray[i]);
105028             }
105029         }
105030         return val;
105031     },
105032
105033     safeParse: function(value, format){
105034         var me = this,
105035             utilDate = Ext.Date,
105036             parsedDate,
105037             result = null;
105038
105039         if (utilDate.formatContainsDateInfo(format)) {
105040             // assume we've been given a full date
105041             result = utilDate.parse(value, format);
105042         } else {
105043             // Use our initial safe date
105044             parsedDate = utilDate.parse(me.initDate + ' ' + value, me.initDateFormat + ' ' + format);
105045             if (parsedDate) {
105046                 result = parsedDate;
105047             }
105048         }
105049         return result;
105050     },
105051
105052     // @private
105053     getSubmitValue: function() {
105054         var me = this,
105055             format = me.submitFormat || me.format,
105056             value = me.getValue();
105057
105058         return value ? Ext.Date.format(value, format) : null;
105059     },
105060
105061     /**
105062      * @private
105063      * Creates the {@link Ext.picker.Time}
105064      */
105065     createPicker: function() {
105066         var me = this,
105067             picker = Ext.create('Ext.picker.Time', {
105068                 selModel: {
105069                     mode: 'SINGLE'
105070                 },
105071                 floating: true,
105072                 hidden: true,
105073                 minValue: me.minValue,
105074                 maxValue: me.maxValue,
105075                 increment: me.increment,
105076                 format: me.format,
105077                 ownerCt: this.ownerCt,
105078                 renderTo: document.body,
105079                 maxHeight: me.pickerMaxHeight,
105080                 focusOnToFront: false
105081             });
105082
105083         me.mon(picker.getSelectionModel(), {
105084             selectionchange: me.onListSelect,
105085             scope: me
105086         });
105087
105088         return picker;
105089     },
105090
105091     /**
105092      * @private
105093      * Enables the key nav for the Time picker when it is expanded.
105094      * TODO this is largely the same logic as ComboBox, should factor out.
105095      */
105096     onExpand: function() {
105097         var me = this,
105098             keyNav = me.pickerKeyNav,
105099             selectOnTab = me.selectOnTab,
105100             picker = me.getPicker(),
105101             lastSelected = picker.getSelectionModel().lastSelected,
105102             itemNode;
105103
105104         if (!keyNav) {
105105             keyNav = me.pickerKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
105106                 boundList: picker,
105107                 forceKeyDown: true,
105108                 tab: function(e) {
105109                     if (selectOnTab) {
105110                         this.selectHighlighted(e);
105111                         me.triggerBlur();
105112                     }
105113                     // Tab key event is allowed to propagate to field
105114                     return true;
105115                 }
105116             });
105117             // stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
105118             if (selectOnTab) {
105119                 me.ignoreMonitorTab = true;
105120             }
105121         }
105122         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
105123
105124         // Highlight the last selected item and scroll it into view
105125         if (lastSelected) {
105126             itemNode = picker.getNode(lastSelected);
105127             if (itemNode) {
105128                 picker.highlightItem(itemNode);
105129                 picker.el.scrollChildIntoView(itemNode, false);
105130             }
105131         }
105132     },
105133
105134     /**
105135      * @private
105136      * Disables the key nav for the Time picker when it is collapsed.
105137      */
105138     onCollapse: function() {
105139         var me = this,
105140             keyNav = me.pickerKeyNav;
105141         if (keyNav) {
105142             keyNav.disable();
105143             me.ignoreMonitorTab = false;
105144         }
105145     },
105146
105147     /**
105148      * @private
105149      * Handles a time being selected from the Time picker.
105150      */
105151     onListSelect: function(list, recordArray) {
105152         var me = this,
105153             record = recordArray[0],
105154             val = record ? record.get('date') : null;
105155         me.setValue(val);
105156         me.fireEvent('select', me, val);
105157         me.picker.clearHighlight();
105158         me.collapse();
105159         me.inputEl.focus();
105160     }
105161 });
105162
105163
105164 /**
105165  * @class Ext.grid.CellEditor
105166  * @extends Ext.Editor
105167  * Internal utility class that provides default configuration for cell editing.
105168  * @ignore
105169  */
105170 Ext.define('Ext.grid.CellEditor', {
105171     extend: 'Ext.Editor',
105172     constructor: function(config) {
105173         if (config.field) {
105174             config.field.monitorTab = false;
105175         }
105176         config.autoSize = {
105177             width: 'boundEl'
105178         };
105179         this.callParent(arguments);
105180     },
105181     
105182     /**
105183      * @private
105184      * Hide the grid cell when editor is shown.
105185      */
105186     onShow: function() {
105187         var first = this.boundEl.first();
105188         if (first) {
105189             first.hide();
105190         }
105191         this.callParent(arguments);
105192     },
105193     
105194     /**
105195      * @private
105196      * Show grid cell when editor is hidden.
105197      */
105198     onHide: function() {
105199         var first = this.boundEl.first();
105200         if (first) {
105201             first.show();
105202         }
105203         this.callParent(arguments);
105204     },
105205     
105206     /**
105207      * @private
105208      * Fix checkbox blur when it is clicked.
105209      */
105210     afterRender: function() {
105211         this.callParent(arguments);
105212         var field = this.field;
105213         if (field.isXType('checkboxfield')) {
105214             field.mon(field.inputEl, 'mousedown', this.onCheckBoxMouseDown, this);
105215             field.mon(field.inputEl, 'click', this.onCheckBoxClick, this);
105216         }
105217     },
105218     
105219     /**
105220      * @private
105221      * Because when checkbox is clicked it loses focus  completeEdit is bypassed.
105222      */
105223     onCheckBoxMouseDown: function() {
105224         this.completeEdit = Ext.emptyFn;
105225     },
105226     
105227     /**
105228      * @private
105229      * Restore checkbox focus and completeEdit method.
105230      */
105231     onCheckBoxClick: function() {
105232         delete this.completeEdit;
105233         this.field.focus(false, 10);
105234     },
105235     
105236     alignment: "tl-tl",
105237     hideEl : false,
105238     cls: Ext.baseCSSPrefix + "small-editor " + Ext.baseCSSPrefix + "grid-editor",
105239     shim: false,
105240     shadow: false
105241 });
105242 /**
105243  * @class Ext.grid.ColumnLayout
105244  * @extends Ext.layout.container.HBox
105245  * @private
105246  *
105247  * <p>This class is used only by the grid's HeaderContainer docked child.</p>
105248  *
105249  * <p>This class adds the ability to shrink the vertical size of the inner container element back if a grouped
105250  * column header has all its child columns dragged out, and the whole HeaderContainer needs to shrink back down.</p>
105251  *
105252  * <p>It also enforces the grid's HeaderContainer's forceFit config by, after every calaculateChildBoxes call, converting
105253  * all pixel widths into flex values, so that propertions are maintained upon width change of the grid.</p>
105254  *
105255  * <p>Also, after every layout, after all headers have attained their 'stretchmax' height, it goes through and calls
105256  * <code>setPadding</code> on the columns so that they lay out correctly. TODO: implement a ColumnHeader component
105257  * layout which takes responsibility for this, and will run upon resize.</p>
105258  */
105259 Ext.define('Ext.grid.ColumnLayout', {
105260     extend: 'Ext.layout.container.HBox',
105261     alias: 'layout.gridcolumn',
105262     type : 'column',
105263
105264     // Height-stretched innerCt must be able to revert back to unstretched height
105265     clearInnerCtOnLayout: false,
105266
105267     constructor: function() {
105268         var me = this;
105269         me.callParent(arguments);
105270         if (!Ext.isDefined(me.availableSpaceOffset)) {
105271             me.availableSpaceOffset = (Ext.getScrollBarWidth() - 2);
105272         }
105273     },
105274
105275     beforeLayout: function() {
105276         var me = this,
105277             i = 0,
105278             items = me.getLayoutItems(),
105279             len = items.length,
105280             item, returnValue;
105281
105282         returnValue = me.callParent(arguments);
105283
105284         // Size to a sane minimum height before possibly being stretched to accommodate grouped headers
105285         me.innerCt.setHeight(23);
105286
105287         // Unstretch child items before the layout which stretches them.
105288         if (me.align == 'stretchmax') {
105289             for (; i < len; i++) {
105290                 item = items[i];
105291                 item.el.setStyle({
105292                     height: 'auto'
105293                 });
105294                 item.titleContainer.setStyle({
105295                     height: 'auto',
105296                     paddingTop: '0'
105297                 });
105298                 if (item.componentLayout && item.componentLayout.lastComponentSize) {
105299                     item.componentLayout.lastComponentSize.height = item.el.dom.offsetHeight;
105300                 }
105301             }
105302         }
105303         return returnValue;
105304     },
105305
105306     // Override to enforce the forceFit config.
105307     calculateChildBoxes: function(visibleItems, targetSize) {
105308         var me = this,
105309             calculations = me.callParent(arguments),
105310             boxes = calculations.boxes,
105311             metaData = calculations.meta,
105312             len = boxes.length, i = 0, box, item;
105313
105314         if (targetSize.width && !me.isColumn) {
105315             // If configured forceFit then all columns will be flexed
105316             if (me.owner.forceFit) {
105317
105318                 for (; i < len; i++) {
105319                     box = boxes[i];
105320                     item = box.component;
105321
105322                     // Set a sane minWidth for the Box layout to be able to squeeze flexed Headers down to.
105323                     item.minWidth = Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
105324
105325                     // For forceFit, just use allocated width as the flex value, and the proportions
105326                     // will end up the same whatever HeaderContainer width they are being forced into.
105327                     item.flex = box.width;
105328                 }
105329
105330                 // Recalculate based upon all columns now being flexed instead of sized.
105331                 calculations = me.callParent(arguments);
105332             }
105333             else if (metaData.tooNarrow) {
105334                 targetSize.width = metaData.desiredSize;
105335             }
105336         }
105337
105338         return calculations;
105339     },
105340
105341     afterLayout: function() {
105342         var me = this,
105343             i = 0,
105344             items = me.getLayoutItems(),
105345             len = items.length;
105346
105347         me.callParent(arguments);
105348
105349         // Set up padding in items
105350         if (me.align == 'stretchmax') {
105351             for (; i < len; i++) {
105352                 items[i].setPadding();
105353             }
105354         }
105355     },
105356
105357     // FIX: when flexing we actually don't have enough space as we would
105358     // typically because of the scrollOffset on the GridView, must reserve this
105359     updateInnerCtSize: function(tSize, calcs) {
105360         var me    = this,
105361             extra = 0;
105362
105363         // Columns must not account for scroll offset
105364         if (!me.isColumn && calcs.meta.tooNarrow) {
105365             if (
105366                 Ext.isWebKit ||
105367                 Ext.isGecko ||
105368                 (Ext.isIEQuirks && (Ext.isIE6 || Ext.isIE7 || Ext.isIE8))
105369             ) {
105370                 extra = 1;
105371             // IE6-8 not quirks
105372             } else if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
105373                 extra = 2;
105374             }
105375             
105376             // this is the 1px accounted for in the Scroller when subtracting 1px.
105377             extra++;
105378             tSize.width = calcs.meta.desiredSize + (me.reserveOffset ? me.availableSpaceOffset : 0) + extra;
105379         }
105380         return me.callParent(arguments);
105381     },
105382
105383     doOwnerCtLayouts: function() {
105384         var ownerCt = this.owner.ownerCt;
105385         if (!ownerCt.componentLayout.layoutBusy) {
105386             ownerCt.doComponentLayout();
105387         }
105388     }
105389 });
105390 /**
105391  * @class Ext.grid.LockingView
105392  * This class is used internally to provide a single interface when using
105393  * a locking grid. Internally, the locking grid creates 2 separate grids,
105394  * so this class is used to map calls appropriately.
105395  * @ignore
105396  */
105397 Ext.define('Ext.grid.LockingView', {
105398     
105399     mixins: {
105400         observable: 'Ext.util.Observable'
105401     },
105402     
105403     eventRelayRe: /^(beforeitem|beforecontainer|item|container|cell)/,
105404     
105405     constructor: function(config){
105406         var me = this,
105407             eventNames = [],
105408             eventRe = me.eventRelayRe,
105409             locked = config.locked.getView(),
105410             normal = config.normal.getView(),
105411             events,
105412             event;
105413         
105414         Ext.apply(me, {
105415             lockedView: locked,
105416             normalView: normal,
105417             lockedGrid: config.locked,
105418             normalGrid: config.normal,
105419             panel: config.panel
105420         });
105421         me.mixins.observable.constructor.call(me, config);
105422         
105423         // relay events
105424         events = locked.events;
105425         for (event in events) {
105426             if (events.hasOwnProperty(event) && eventRe.test(event)) {
105427                 eventNames.push(event);
105428             }
105429         }
105430         me.relayEvents(locked, eventNames);
105431         me.relayEvents(normal, eventNames);
105432         
105433         normal.on({
105434             scope: me,
105435             itemmouseleave: me.onItemMouseLeave,
105436             itemmouseenter: me.onItemMouseEnter
105437         });
105438         
105439         locked.on({
105440             scope: me,
105441             itemmouseleave: me.onItemMouseLeave,
105442             itemmouseenter: me.onItemMouseEnter
105443         });
105444     },
105445     
105446     getGridColumns: function() {
105447         var cols = this.lockedGrid.headerCt.getGridColumns();
105448         return cols.concat(this.normalGrid.headerCt.getGridColumns());
105449     },
105450     
105451     onItemMouseEnter: function(view, record){
105452         var me = this,
105453             locked = me.lockedView,
105454             other = me.normalView,
105455             item;
105456             
105457         if (view.trackOver) {
105458             if (view !== locked) {
105459                 other = locked;
105460             }
105461             item = other.getNode(record);
105462             other.highlightItem(item);
105463         }
105464     },
105465     
105466     onItemMouseLeave: function(view, record){
105467         var me = this,
105468             locked = me.lockedView,
105469             other = me.normalView;
105470             
105471         if (view.trackOver) {
105472             if (view !== locked) {
105473                 other = locked;
105474             }
105475             other.clearHighlight();
105476         }
105477     },
105478     
105479     relayFn: function(name, args){
105480         args = args || [];
105481         
105482         var view = this.lockedView;
105483         view[name].apply(view, args || []);    
105484         view = this.normalView;
105485         view[name].apply(view, args || []);   
105486     },
105487     
105488     getSelectionModel: function(){
105489         return this.panel.getSelectionModel();    
105490     },
105491     
105492     getStore: function(){
105493         return this.panel.store;
105494     },
105495     
105496     getNode: function(nodeInfo){
105497         // default to the normal view
105498         return this.normalView.getNode(nodeInfo);
105499     },
105500     
105501     getCell: function(record, column){
105502         var view = this.lockedView,
105503             row;
105504         
105505         
105506         if (view.getHeaderAtIndex(column) === -1) {
105507             view = this.normalView;
105508         }
105509         
105510         row = view.getNode(record);
105511         return Ext.fly(row).down(column.getCellSelector());
105512     },
105513     
105514     getRecord: function(node){
105515         var result = this.lockedView.getRecord(node);
105516         if (!node) {
105517             result = this.normalView.getRecord(node);
105518         }
105519         return result;
105520     },
105521     
105522     addElListener: function(eventName, fn, scope){
105523         this.relayFn('addElListener', arguments);
105524     },
105525     
105526     refreshNode: function(){
105527         this.relayFn('refreshNode', arguments);
105528     },
105529     
105530     refresh: function(){
105531         this.relayFn('refresh', arguments);
105532     },
105533     
105534     bindStore: function(){
105535         this.relayFn('bindStore', arguments);
105536     },
105537     
105538     addRowCls: function(){
105539         this.relayFn('addRowCls', arguments);
105540     },
105541     
105542     removeRowCls: function(){
105543         this.relayFn('removeRowCls', arguments);
105544     }
105545        
105546 });
105547 /**
105548  * @class Ext.grid.Lockable
105549  * @private
105550  *
105551  * Lockable is a private mixin which injects lockable behavior into any
105552  * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
105553  * automatically inject the Ext.grid.Lockable mixin in when one of the
105554  * these conditions are met:
105555  * - The TablePanel has the lockable configuration set to true
105556  * - One of the columns in the TablePanel has locked set to true/false
105557  *
105558  * Each TablePanel subclass *must* register an alias. It should have an array
105559  * of configurations to copy to the 2 separate tablepanel's that will be generated
105560  * to note what configurations should be copied. These are named normalCfgCopy and
105561  * lockedCfgCopy respectively.
105562  *
105563  * Columns which are locked must specify a fixed width. They do *NOT* support a
105564  * flex width.
105565  *
105566  * Configurations which are specified in this class will be available on any grid or
105567  * tree which is using the lockable functionality.
105568  */
105569 Ext.define('Ext.grid.Lockable', {
105570     
105571     requires: ['Ext.grid.LockingView'],
105572     
105573     /**
105574      * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
105575      * locked grid view. This is turned on by default. If your grid is guaranteed
105576      * to have rows of all the same height, you should set this to false to
105577      * optimize performance.
105578      */
105579     syncRowHeight: true,
105580     
105581     /**
105582      * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
105583      * not specified lockable will determine the subgrid xtype to create by the
105584      * following rule. Use the superclasses xtype if the superclass is NOT
105585      * tablepanel, otherwise use the xtype itself.
105586      */
105587     
105588     /**
105589      * @cfg {Object} lockedViewConfig A view configuration to be applied to the
105590      * locked side of the grid. Any conflicting configurations between lockedViewConfig
105591      * and viewConfig will be overwritten by the lockedViewConfig.
105592      */
105593
105594     /**
105595      * @cfg {Object} normalViewConfig A view configuration to be applied to the
105596      * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
105597      * and viewConfig will be overwritten by the normalViewConfig.
105598      */
105599     
105600     // private variable to track whether or not the spacer is hidden/visible
105601     spacerHidden: true,
105602     
105603     // i8n text
105604     unlockText: 'Unlock',
105605     lockText: 'Lock',
105606     
105607     determineXTypeToCreate: function() {
105608         var me = this,
105609             typeToCreate;
105610
105611         if (me.subGridXType) {
105612             typeToCreate = me.subGridXType;
105613         } else {
105614             var xtypes     = this.getXTypes().split('/'),
105615                 xtypesLn   = xtypes.length,
105616                 xtype      = xtypes[xtypesLn - 1],
105617                 superxtype = xtypes[xtypesLn - 2];
105618                 
105619             if (superxtype !== 'tablepanel') {
105620                 typeToCreate = superxtype;
105621             } else {
105622                 typeToCreate = xtype;
105623             }
105624         }
105625         
105626         return typeToCreate;
105627     },
105628     
105629     // injectLockable will be invoked before initComponent's parent class implementation
105630     // is called, so throughout this method this. are configurations
105631     injectLockable: function() {
105632         // ensure lockable is set to true in the TablePanel
105633         this.lockable = true;
105634         // Instruct the TablePanel it already has a view and not to create one.
105635         // We are going to aggregate 2 copies of whatever TablePanel we are using
105636         this.hasView = true;
105637
105638         var me = this,
105639             // xtype of this class, 'treepanel' or 'gridpanel'
105640             // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
105641             // alias.)
105642             xtype = me.determineXTypeToCreate(),
105643             // share the selection model
105644             selModel = me.getSelectionModel(),
105645             lockedGrid = {
105646                 xtype: xtype,
105647                 // Lockable does NOT support animations for Tree
105648                 enableAnimations: false,
105649                 scroll: false,
105650                 scrollerOwner: false,
105651                 selModel: selModel,
105652                 border: false,
105653                 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
105654             },
105655             normalGrid = {
105656                 xtype: xtype,
105657                 enableAnimations: false,
105658                 scrollerOwner: false,
105659                 selModel: selModel,
105660                 border: false
105661             },
105662             i = 0,
105663             columns,
105664             lockedHeaderCt,
105665             normalHeaderCt;
105666         
105667         me.addCls(Ext.baseCSSPrefix + 'grid-locked');
105668         
105669         // copy appropriate configurations to the respective
105670         // aggregated tablepanel instances and then delete them
105671         // from the master tablepanel.
105672         Ext.copyTo(normalGrid, me, me.normalCfgCopy);
105673         Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
105674         for (; i < me.normalCfgCopy.length; i++) {
105675             delete me[me.normalCfgCopy[i]];
105676         }
105677         for (i = 0; i < me.lockedCfgCopy.length; i++) {
105678             delete me[me.lockedCfgCopy[i]];
105679         }
105680         
105681         me.lockedHeights = [];
105682         me.normalHeights = [];
105683         
105684         columns = me.processColumns(me.columns);
105685
105686         lockedGrid.width = columns.lockedWidth;
105687         lockedGrid.columns = columns.locked;
105688         normalGrid.columns = columns.normal;
105689         
105690         me.store = Ext.StoreManager.lookup(me.store);
105691         lockedGrid.store = me.store;
105692         normalGrid.store = me.store;
105693         
105694         // normal grid should flex the rest of the width
105695         normalGrid.flex = 1;
105696         lockedGrid.viewConfig = me.lockedViewConfig || {};
105697         lockedGrid.viewConfig.loadingUseMsg = false;
105698         normalGrid.viewConfig = me.normalViewConfig || {};
105699         
105700         Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
105701         Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
105702         
105703         me.normalGrid = Ext.ComponentManager.create(normalGrid);
105704         me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
105705         
105706         me.view = Ext.create('Ext.grid.LockingView', {
105707             locked: me.lockedGrid,
105708             normal: me.normalGrid,
105709             panel: me    
105710         });
105711         
105712         if (me.syncRowHeight) {
105713             me.lockedGrid.getView().on({
105714                 refresh: me.onLockedGridAfterRefresh,
105715                 itemupdate: me.onLockedGridAfterUpdate,
105716                 scope: me
105717             });
105718             
105719             me.normalGrid.getView().on({
105720                 refresh: me.onNormalGridAfterRefresh,
105721                 itemupdate: me.onNormalGridAfterUpdate,
105722                 scope: me
105723             });
105724         }
105725         
105726         lockedHeaderCt = me.lockedGrid.headerCt;
105727         normalHeaderCt = me.normalGrid.headerCt;
105728         
105729         lockedHeaderCt.lockedCt = true;
105730         lockedHeaderCt.lockableInjected = true;
105731         normalHeaderCt.lockableInjected = true;
105732         
105733         lockedHeaderCt.on({
105734             columnshow: me.onLockedHeaderShow,
105735             columnhide: me.onLockedHeaderHide,
105736             columnmove: me.onLockedHeaderMove,
105737             sortchange: me.onLockedHeaderSortChange,
105738             columnresize: me.onLockedHeaderResize,
105739             scope: me
105740         });
105741         
105742         normalHeaderCt.on({
105743             columnmove: me.onNormalHeaderMove,
105744             sortchange: me.onNormalHeaderSortChange,
105745             scope: me
105746         });
105747         
105748         me.normalGrid.on({
105749             scrollershow: me.onScrollerShow,
105750             scrollerhide: me.onScrollerHide,
105751             scope: me
105752         });
105753         
105754         me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
105755         
105756         me.modifyHeaderCt();
105757         me.items = [me.lockedGrid, me.normalGrid];
105758
105759         me.layout = {
105760             type: 'hbox',
105761             align: 'stretch'
105762         };
105763     },
105764     
105765     processColumns: function(columns){
105766         // split apart normal and lockedWidths
105767         var i = 0,
105768             len = columns.length,
105769             lockedWidth = 0,
105770             lockedHeaders = [],
105771             normalHeaders = [],
105772             column;
105773             
105774         for (; i < len; ++i) {
105775             column = columns[i];
105776             // mark the column as processed so that the locked attribute does not
105777             // trigger trying to aggregate the columns again.
105778             column.processed = true;
105779             if (column.locked) {
105780                 if (column.flex) {
105781                     Ext.Error.raise("Columns which are locked do NOT support a flex width. You must set a width on the " + columns[i].text + "column.");
105782                 }
105783                 lockedWidth += column.width;
105784                 lockedHeaders.push(column);
105785             } else {
105786                 normalHeaders.push(column);
105787             }
105788         }
105789         return {
105790             lockedWidth: lockedWidth,
105791             locked: lockedHeaders,
105792             normal: normalHeaders    
105793         };
105794     },
105795     
105796     // create a new spacer after the table is refreshed
105797     onLockedGridAfterLayout: function() {
105798         var me         = this,
105799             lockedView = me.lockedGrid.getView();
105800         lockedView.on({
105801             refresh: me.createSpacer,
105802             beforerefresh: me.destroySpacer,
105803             scope: me
105804         });
105805     },
105806     
105807     // trigger a pseudo refresh on the normal side
105808     onLockedHeaderMove: function() {
105809         if (this.syncRowHeight) {
105810             this.onNormalGridAfterRefresh();
105811         }
105812     },
105813     
105814     // trigger a pseudo refresh on the locked side
105815     onNormalHeaderMove: function() {
105816         if (this.syncRowHeight) {
105817             this.onLockedGridAfterRefresh();
105818         }
105819     },
105820     
105821     // create a spacer in lockedsection and store a reference
105822     // TODO: Should destroy before refreshing content
105823     createSpacer: function() {
105824         var me   = this,
105825             // This affects scrolling all the way to the bottom of a locked grid
105826             // additional test, sort a column and make sure it synchronizes
105827             w    = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0),
105828             view = me.lockedGrid.getView(),
105829             el   = view.el;
105830
105831         me.spacerEl = Ext.core.DomHelper.append(el, {
105832             cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
105833             style: 'height: ' + w + 'px;'
105834         }, true);
105835     },
105836     
105837     destroySpacer: function() {
105838         var me = this;
105839         if (me.spacerEl) {
105840             me.spacerEl.destroy();
105841             delete me.spacerEl;
105842         }
105843     },
105844     
105845     // cache the heights of all locked rows and sync rowheights
105846     onLockedGridAfterRefresh: function() {
105847         var me     = this,
105848             view   = me.lockedGrid.getView(),
105849             el     = view.el,
105850             rowEls = el.query(view.getItemSelector()),
105851             ln     = rowEls.length,
105852             i = 0;
105853             
105854         // reset heights each time.
105855         me.lockedHeights = [];
105856         
105857         for (; i < ln; i++) {
105858             me.lockedHeights[i] = rowEls[i].clientHeight;
105859         }
105860         me.syncRowHeights();
105861     },
105862     
105863     // cache the heights of all normal rows and sync rowheights
105864     onNormalGridAfterRefresh: function() {
105865         var me     = this,
105866             view   = me.normalGrid.getView(),
105867             el     = view.el,
105868             rowEls = el.query(view.getItemSelector()),
105869             ln     = rowEls.length,
105870             i = 0;
105871             
105872         // reset heights each time.
105873         me.normalHeights = [];
105874         
105875         for (; i < ln; i++) {
105876             me.normalHeights[i] = rowEls[i].clientHeight;
105877         }
105878         me.syncRowHeights();
105879     },
105880     
105881     // rows can get bigger/smaller
105882     onLockedGridAfterUpdate: function(record, index, node) {
105883         this.lockedHeights[index] = node.clientHeight;
105884         this.syncRowHeights();
105885     },
105886     
105887     // rows can get bigger/smaller
105888     onNormalGridAfterUpdate: function(record, index, node) {
105889         this.normalHeights[index] = node.clientHeight;
105890         this.syncRowHeights();
105891     },
105892     
105893     // match the rowheights to the biggest rowheight on either
105894     // side
105895     syncRowHeights: function() {
105896         var me = this,
105897             lockedHeights = me.lockedHeights,
105898             normalHeights = me.normalHeights,
105899             calcHeights   = [],
105900             ln = lockedHeights.length,
105901             i  = 0,
105902             lockedView, normalView,
105903             lockedRowEls, normalRowEls,
105904             vertScroller = me.getVerticalScroller(),
105905             scrollTop;
105906
105907         // ensure there are an equal num of locked and normal
105908         // rows before synchronization
105909         if (lockedHeights.length && normalHeights.length) {
105910             lockedView = me.lockedGrid.getView();
105911             normalView = me.normalGrid.getView();
105912             lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
105913             normalRowEls = normalView.el.query(normalView.getItemSelector());
105914
105915             // loop thru all of the heights and sync to the other side
105916             for (; i < ln; i++) {
105917                 // ensure both are numbers
105918                 if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
105919                     if (lockedHeights[i] > normalHeights[i]) {
105920                         Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
105921                     } else if (lockedHeights[i] < normalHeights[i]) {
105922                         Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
105923                     }
105924                 }
105925             }
105926
105927             // invalidate the scroller and sync the scrollers
105928             me.normalGrid.invalidateScroller();
105929             
105930             // synchronize the view with the scroller, if we have a virtualScrollTop
105931             // then the user is using a PagingScroller 
105932             if (vertScroller && vertScroller.setViewScrollTop) {
105933                 vertScroller.setViewScrollTop(me.virtualScrollTop);
105934             } else {
105935                 // We don't use setScrollTop here because if the scrollTop is
105936                 // set to the exact same value some browsers won't fire the scroll
105937                 // event. Instead, we directly set the scrollTop.
105938                 scrollTop = normalView.el.dom.scrollTop;
105939                 normalView.el.dom.scrollTop = scrollTop;
105940                 lockedView.el.dom.scrollTop = scrollTop;
105941             }
105942             
105943             // reset the heights
105944             me.lockedHeights = [];
105945             me.normalHeights = [];
105946         }
105947     },
105948     
105949     // track when scroller is shown
105950     onScrollerShow: function(scroller, direction) {
105951         if (direction === 'horizontal') {
105952             this.spacerHidden = false;
105953             this.spacerEl.removeCls(Ext.baseCSSPrefix + 'hidden');
105954         }
105955     },
105956     
105957     // track when scroller is hidden
105958     onScrollerHide: function(scroller, direction) {
105959         if (direction === 'horizontal') {
105960             this.spacerHidden = true;
105961             this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
105962         }
105963     },
105964
105965     
105966     // inject Lock and Unlock text
105967     modifyHeaderCt: function() {
105968         var me = this;
105969         me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
105970         me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
105971     },
105972     
105973     onUnlockMenuClick: function() {
105974         this.unlock();
105975     },
105976     
105977     onLockMenuClick: function() {
105978         this.lock();
105979     },
105980     
105981     getMenuItems: function(locked) {
105982         var me            = this,
105983             unlockText    = me.unlockText,
105984             lockText      = me.lockText,
105985             // TODO: Refactor to use Ext.baseCSSPrefix
105986             unlockCls     = 'xg-hmenu-unlock',
105987             lockCls       = 'xg-hmenu-lock',
105988             unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
105989             lockHandler   = Ext.Function.bind(me.onLockMenuClick, me);
105990         
105991         // runs in the scope of headerCt
105992         return function() {
105993             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
105994             o.push('-',{
105995                 cls: unlockCls,
105996                 text: unlockText,
105997                 handler: unlockHandler,
105998                 disabled: !locked
105999             });
106000             o.push({
106001                 cls: lockCls,
106002                 text: lockText,
106003                 handler: lockHandler,
106004                 disabled: locked
106005             });
106006             return o;
106007         };
106008     },
106009     
106010     // going from unlocked section to locked
106011     /**
106012      * Locks the activeHeader as determined by which menu is open OR a header
106013      * as specified.
106014      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
106015      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to appending as the last item.
106016      * @private
106017      */
106018     lock: function(activeHd, toIdx) {
106019         var me         = this,
106020             normalGrid = me.normalGrid,
106021             lockedGrid = me.lockedGrid,
106022             normalHCt  = normalGrid.headerCt,
106023             lockedHCt  = lockedGrid.headerCt;
106024             
106025         activeHd = activeHd || normalHCt.getMenu().activeHeader;
106026         
106027         // if column was previously flexed, get/set current width
106028         // and remove the flex
106029         if (activeHd.flex) {
106030             activeHd.width = activeHd.getWidth();
106031             delete activeHd.flex;
106032         }
106033         
106034         normalHCt.remove(activeHd, false);
106035         lockedHCt.suspendLayout = true;
106036         if (Ext.isDefined(toIdx)) {
106037             lockedHCt.insert(toIdx, activeHd);
106038         } else {
106039             lockedHCt.add(activeHd);
106040         }
106041         lockedHCt.suspendLayout = false;
106042         me.syncLockedSection();
106043     },
106044     
106045     syncLockedSection: function() {
106046         var me = this;
106047         me.syncLockedWidth();
106048         me.lockedGrid.getView().refresh();
106049         me.normalGrid.getView().refresh();
106050     },
106051     
106052     // adjust the locked section to the width of its respective
106053     // headerCt
106054     syncLockedWidth: function() {
106055         var me = this,
106056             width = me.lockedGrid.headerCt.getFullWidth(true);
106057         me.lockedGrid.setWidth(width);
106058     },
106059     
106060     onLockedHeaderResize: function() {
106061         this.syncLockedWidth();
106062     },
106063     
106064     onLockedHeaderHide: function() {
106065         this.syncLockedWidth();
106066     },
106067     
106068     onLockedHeaderShow: function() {
106069         this.syncLockedWidth();
106070     },
106071     
106072     onLockedHeaderSortChange: function(headerCt, header, sortState) {
106073         if (sortState) {
106074             // no real header, and silence the event so we dont get into an
106075             // infinite loop
106076             this.normalGrid.headerCt.clearOtherSortStates(null, true);
106077         }
106078     },
106079     
106080     onNormalHeaderSortChange: function(headerCt, header, sortState) {
106081         if (sortState) {
106082             // no real header, and silence the event so we dont get into an
106083             // infinite loop
106084             this.lockedGrid.headerCt.clearOtherSortStates(null, true);
106085         }
106086     },
106087     
106088     // going from locked section to unlocked
106089     /**
106090      * Unlocks the activeHeader as determined by which menu is open OR a header
106091      * as specified.
106092      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
106093      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to 0.
106094      * @private
106095      */
106096     unlock: function(activeHd, toIdx) {
106097         var me         = this,
106098             normalGrid = me.normalGrid,
106099             lockedGrid = me.lockedGrid,
106100             normalHCt  = normalGrid.headerCt,
106101             lockedHCt  = lockedGrid.headerCt;
106102
106103         if (!Ext.isDefined(toIdx)) {
106104             toIdx = 0;
106105         }
106106         activeHd = activeHd || lockedHCt.getMenu().activeHeader;
106107         
106108         lockedHCt.remove(activeHd, false);
106109         me.syncLockedWidth();
106110         me.lockedGrid.getView().refresh();
106111         normalHCt.insert(toIdx, activeHd);
106112         me.normalGrid.getView().refresh();
106113     },
106114     
106115     // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
106116     reconfigureLockable: function(store, columns) {
106117         var me = this,
106118             lockedGrid = me.lockedGrid,
106119             normalGrid = me.normalGrid;
106120         
106121         if (columns) {
106122             lockedGrid.headerCt.removeAll();
106123             normalGrid.headerCt.removeAll();
106124             
106125             columns = me.processColumns(columns);
106126             lockedGrid.setWidth(columns.lockedWidth);
106127             lockedGrid.headerCt.add(columns.locked);
106128             normalGrid.headerCt.add(columns.normal);
106129         }
106130         
106131         if (store) {
106132             store = Ext.data.StoreManager.lookup(store);
106133             me.store = store;
106134             lockedGrid.bindStore(store);
106135             normalGrid.bindStore(store);
106136         } else {
106137             lockedGrid.getView().refresh();
106138             normalGrid.getView().refresh();
106139         }
106140     }
106141 });
106142
106143 /**
106144  * @class Ext.grid.Scroller
106145  * @extends Ext.Component
106146  *
106147  * Docked in an Ext.grid.Panel, controls virtualized scrolling and synchronization
106148  * across different sections.
106149  *
106150  * @private
106151  */
106152 Ext.define('Ext.grid.Scroller', {
106153     extend: 'Ext.Component',
106154     alias: 'widget.gridscroller',
106155     weight: 110,
106156     cls: Ext.baseCSSPrefix + 'scroller',
106157     focusable: false,
106158     
106159     renderTpl: ['<div class="' + Ext.baseCSSPrefix + 'stretcher"></div>'],
106160     
106161     initComponent: function() {
106162         var me       = this,
106163             dock     = me.dock,
106164             cls      = Ext.baseCSSPrefix + 'scroller-vertical',
106165             sizeProp = 'width',
106166             // Subtracting 2px would give us a perfect fit of the scroller
106167             // however, some browsers wont allow us to scroll content thats not
106168             // visible, therefore we use 1px.
106169             // Note: This 1px offset matches code in Ext.grid.ColumnLayout when
106170             // reserving room for the scrollbar
106171             scrollbarWidth = Ext.getScrollBarWidth() + (Ext.isIE ? 1 : -1);
106172
106173         me.offsets = {bottom: 0};
106174
106175         if (dock === 'top' || dock === 'bottom') {
106176             cls = Ext.baseCSSPrefix + 'scroller-horizontal';
106177             sizeProp = 'height';
106178         }
106179         me[sizeProp] = scrollbarWidth;
106180         
106181         me.cls += (' ' + cls);
106182         
106183         Ext.applyIf(me.renderSelectors, {
106184             stretchEl: '.' + Ext.baseCSSPrefix + 'stretcher'
106185         });
106186         me.callParent();
106187     },
106188     
106189     
106190     afterRender: function() {
106191         var me = this;
106192         me.callParent();
106193         me.ownerCt.on('afterlayout', me.onOwnerAfterLayout, me);
106194         me.mon(me.el, 'scroll', me.onElScroll, me);
106195         Ext.cache[me.el.id].skipGarbageCollection = true;
106196     },
106197     
106198     getSizeCalculation: function() {
106199         var owner  = this.getPanel(),
106200             dock   = this.dock,
106201             elDom  = this.el.dom,
106202             width  = 1,
106203             height = 1,
106204             view, tbl;
106205             
106206         if (dock === 'top' || dock === 'bottom') {
106207             // TODO: Must gravitate to a single region..
106208             // Horizontal scrolling only scrolls virtualized region
106209             var items  = owner.query('tableview'),
106210                 center = items[1] || items[0];
106211             
106212             if (!center) {
106213                 return false;
106214             }
106215             // center is not guaranteed to have content, such as when there
106216             // are zero rows in the grid/tree. We read the width from the
106217             // headerCt instead.
106218             width = center.headerCt.getFullWidth();
106219             
106220             if (Ext.isIEQuirks) {
106221                 width--;
106222             }
106223             // Account for the 1px removed in Scroller.
106224             width--;
106225         } else {            
106226             view = owner.down('tableview:not([lockableInjected])');
106227             if (!view) {
106228                 return false;
106229             }
106230             tbl = view.el;
106231             if (!tbl) {
106232                 return false;
106233             }
106234             
106235             // needs to also account for header and scroller (if still in picture)
106236             // should calculate from headerCt.
106237             height = tbl.dom.scrollHeight;
106238         }
106239         if (isNaN(width)) {
106240             width = 1;
106241         }
106242         if (isNaN(height)) {
106243             height = 1;
106244         }
106245         return {
106246             width: width,
106247             height: height
106248         };
106249     },
106250     
106251     invalidate: function(firstPass) {
106252         if (!this.stretchEl || !this.ownerCt) {
106253             return;
106254         }
106255         var size  = this.getSizeCalculation(),
106256             elDom = this.el.dom;
106257         if (size) {
106258             this.stretchEl.setSize(size);
106259         
106260             // BrowserBug: IE7
106261             // This makes the scroller enabled, when initially rendering.
106262             elDom.scrollTop = elDom.scrollTop;
106263         }
106264     },
106265
106266     onOwnerAfterLayout: function(owner, layout) {
106267         this.invalidate();
106268     },
106269
106270     /**
106271      * Sets the scrollTop and constrains the value between 0 and max.
106272      * @param {Number} scrollTop
106273      * @return {Number} The resulting scrollTop value after being constrained
106274      */
106275     setScrollTop: function(scrollTop) {
106276         if (this.el) {
106277             var elDom = this.el.dom;
106278             return elDom.scrollTop = Ext.Number.constrain(scrollTop, 0, elDom.scrollHeight - elDom.clientHeight);
106279         }
106280     },
106281
106282     /**
106283      * Sets the scrollLeft and constrains the value between 0 and max.
106284      * @param {Number} scrollLeft
106285      * @return {Number} The resulting scrollLeft value after being constrained
106286      */
106287     setScrollLeft: function(scrollLeft) {
106288         if (this.el) {
106289             var elDom = this.el.dom;
106290             return elDom.scrollLeft = Ext.Number.constrain(scrollLeft, 0, elDom.scrollWidth - elDom.clientWidth);
106291         }
106292     },
106293
106294     /**
106295      * Scroll by deltaY
106296      * @param {Number} delta
106297      * @return {Number} The resulting scrollTop value
106298      */
106299     scrollByDeltaY: function(delta) {
106300         if (this.el) {
106301             var elDom = this.el.dom;
106302             return this.setScrollTop(elDom.scrollTop + delta);
106303         }
106304     },
106305
106306     /**
106307      * Scroll by deltaX
106308      * @param {Number} delta
106309      * @return {Number} The resulting scrollLeft value
106310      */
106311     scrollByDeltaX: function(delta) {
106312         if (this.el) {
106313             var elDom = this.el.dom;
106314             return this.setScrollLeft(elDom.scrollLeft + delta);
106315         }
106316     },
106317     
106318     
106319     /**
106320      * Scroll to the top.
106321      */
106322     scrollToTop : function(){
106323         this.setScrollTop(0);
106324     },
106325     
106326     // synchronize the scroller with the bound gridviews
106327     onElScroll: function(event, target) {
106328         this.fireEvent('bodyscroll', event, target);
106329     },
106330
106331     getPanel: function() {
106332         var me = this;
106333         if (!me.panel) {
106334             me.panel = this.up('[scrollerOwner]');
106335         }
106336         return me.panel;
106337     }
106338 });
106339
106340
106341 /**
106342  * @class Ext.grid.PagingScroller
106343  * @extends Ext.grid.Scroller
106344  *
106345  * @private
106346  */
106347 Ext.define('Ext.grid.PagingScroller', {
106348     extend: 'Ext.grid.Scroller',
106349     alias: 'widget.paginggridscroller',
106350     //renderTpl: null,
106351     //tpl: [
106352     //    '<tpl for="pages">',
106353     //        '<div class="' + Ext.baseCSSPrefix + 'stretcher" style="width: {width}px;height: {height}px;"></div>',
106354     //    '</tpl>'
106355     //],
106356     /**
106357      * @cfg {Number} percentageFromEdge This is a number above 0 and less than 1 which specifies
106358      * at what percentage to begin fetching the next page. For example if the pageSize is 100
106359      * and the percentageFromEdge is the default of 0.35, the paging scroller will prefetch pages
106360      * when scrolling up between records 0 and 34 and when scrolling down between records 65 and 99.
106361      */
106362     percentageFromEdge: 0.35,
106363     
106364     /**
106365      * @cfg {Number} scrollToLoadBuffer This is the time in milliseconds to buffer load requests
106366      * when scrolling the PagingScrollbar.
106367      */
106368     scrollToLoadBuffer: 200,
106369     
106370     activePrefetch: true,
106371     
106372     chunkSize: 50,
106373     snapIncrement: 25,
106374     
106375     syncScroll: true,
106376     
106377     initComponent: function() {
106378         var me = this,
106379             ds = me.store;
106380
106381         ds.on('guaranteedrange', this.onGuaranteedRange, this);
106382         this.callParent(arguments);
106383     },
106384     
106385     
106386     onGuaranteedRange: function(range, start, end) {
106387         var me = this,
106388             ds = me.store,
106389             rs;
106390         // this should never happen
106391         if (range.length && me.visibleStart < range[0].index) {
106392             return;
106393         }
106394         
106395         ds.loadRecords(range);
106396
106397         if (!me.firstLoad) {
106398             if (me.rendered) {
106399                 me.invalidate();
106400             } else {
106401                 me.on('afterrender', this.invalidate, this, {single: true});
106402             }
106403             me.firstLoad = true;
106404         } else {
106405             // adjust to visible
106406             me.syncTo();
106407         }
106408     },
106409     
106410     syncTo: function() {
106411         var me            = this,
106412             pnl           = me.getPanel(),
106413             store         = pnl.store,
106414             scrollerElDom = this.el.dom,
106415             rowOffset     = me.visibleStart - store.guaranteedStart,
106416             scrollBy      = rowOffset * me.rowHeight,
106417             scrollHeight  = scrollerElDom.scrollHeight,
106418             clientHeight  = scrollerElDom.clientHeight,
106419             scrollTop     = scrollerElDom.scrollTop,
106420             useMaximum;
106421         
106422         // BrowserBug: clientHeight reports 0 in IE9 StrictMode
106423         // Instead we are using offsetHeight and hardcoding borders
106424         if (Ext.isIE9 && Ext.isStrict) {
106425             clientHeight = scrollerElDom.offsetHeight + 2;
106426         }
106427
106428         // This should always be zero or greater than zero but staying
106429         // safe and less than 0 we'll scroll to the bottom.        
106430         useMaximum = (scrollHeight - clientHeight - scrollTop <= 0);
106431         this.setViewScrollTop(scrollBy, useMaximum);
106432     },
106433     
106434     getPageData : function(){
106435         var panel = this.getPanel(),
106436             store = panel.store,
106437             totalCount = store.getTotalCount();
106438             
106439         return {
106440             total : totalCount,
106441             currentPage : store.currentPage,
106442             pageCount: Math.ceil(totalCount / store.pageSize),
106443             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
106444             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
106445         };
106446     },
106447     
106448     onElScroll: function(e, t) {
106449         var me = this,
106450             panel = me.getPanel(),
106451             store = panel.store,
106452             pageSize = store.pageSize,
106453             guaranteedStart = store.guaranteedStart,
106454             guaranteedEnd = store.guaranteedEnd,
106455             totalCount = store.getTotalCount(),
106456             numFromEdge = Math.ceil(me.percentageFromEdge * store.pageSize),
106457             position = t.scrollTop,
106458             visibleStart = Math.floor(position / me.rowHeight),
106459             view = panel.down('tableview'),
106460             viewEl = view.el,
106461             visibleHeight = viewEl.getHeight(),
106462             visibleAhead = Math.ceil(visibleHeight / me.rowHeight),
106463             visibleEnd = visibleStart + visibleAhead,
106464             prevPage = Math.floor(visibleStart / store.pageSize),
106465             nextPage = Math.floor(visibleEnd / store.pageSize) + 2,
106466             lastPage = Math.ceil(totalCount / store.pageSize),
106467             //requestStart = visibleStart,
106468             requestStart = Math.floor(visibleStart / me.snapIncrement) * me.snapIncrement,
106469             requestEnd = requestStart + pageSize - 1,
106470             activePrefetch = me.activePrefetch;
106471
106472         me.visibleStart = visibleStart;
106473         me.visibleEnd = visibleEnd;
106474         
106475         
106476         me.syncScroll = true;
106477         if (totalCount >= pageSize) {
106478             // end of request was past what the total is, grab from the end back a pageSize
106479             if (requestEnd > totalCount - 1) {
106480                 this.cancelLoad();
106481                 if (store.rangeSatisfied(totalCount - pageSize, totalCount - 1)) {
106482                     me.syncScroll = true;
106483                 }
106484                 store.guaranteeRange(totalCount - pageSize, totalCount - 1);
106485             // Out of range, need to reset the current data set
106486             } else if (visibleStart < guaranteedStart || visibleEnd > guaranteedEnd) {
106487                 if (store.rangeSatisfied(requestStart, requestEnd)) {
106488                     this.cancelLoad();
106489                     store.guaranteeRange(requestStart, requestEnd);
106490                 } else {
106491                     store.mask();
106492                     me.attemptLoad(requestStart, requestEnd);
106493                 }
106494                 // dont sync the scroll view immediately, sync after the range has been guaranteed
106495                 me.syncScroll = false;
106496             } else if (activePrefetch && visibleStart < (guaranteedStart + numFromEdge) && prevPage > 0) {
106497                 me.syncScroll = true;
106498                 store.prefetchPage(prevPage);
106499             } else if (activePrefetch && visibleEnd > (guaranteedEnd - numFromEdge) && nextPage < lastPage) {
106500                 me.syncScroll = true;
106501                 store.prefetchPage(nextPage);
106502             }
106503         }
106504     
106505     
106506         if (me.syncScroll) {
106507             me.syncTo();
106508         }
106509     },
106510     
106511     getSizeCalculation: function() {
106512         // Use the direct ownerCt here rather than the scrollerOwner
106513         // because we are calculating widths/heights.
106514         var owner = this.ownerCt,
106515             view   = owner.getView(),
106516             store  = this.store,
106517             dock   = this.dock,
106518             elDom  = this.el.dom,
106519             width  = 1,
106520             height = 1;
106521         
106522         if (!this.rowHeight) {
106523             this.rowHeight = view.el.down(view.getItemSelector()).getHeight(false, true);
106524         }
106525
106526         height = store.getTotalCount() * this.rowHeight;
106527
106528         if (isNaN(width)) {
106529             width = 1;
106530         }
106531         if (isNaN(height)) {
106532             height = 1;
106533         }
106534         return {
106535             width: width,
106536             height: height
106537         };
106538     },
106539     
106540     attemptLoad: function(start, end) {
106541         var me = this;
106542         if (!me.loadTask) {
106543             me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
106544         }
106545         me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
106546     },
106547     
106548     cancelLoad: function() {
106549         if (this.loadTask) {
106550             this.loadTask.cancel();
106551         }
106552     },
106553     
106554     doAttemptLoad:  function(start, end) {
106555         var store = this.getPanel().store;
106556         store.guaranteeRange(start, end);
106557     },
106558     
106559     setViewScrollTop: function(scrollTop, useMax) {
106560         var owner = this.getPanel(),
106561             items = owner.query('tableview'),
106562             i = 0,
106563             len = items.length,
106564             center,
106565             centerEl,
106566             calcScrollTop,
106567             maxScrollTop,
106568             scrollerElDom = this.el.dom;
106569             
106570         owner.virtualScrollTop = scrollTop;
106571             
106572         center = items[1] || items[0];
106573         centerEl = center.el.dom;
106574         
106575         maxScrollTop = ((owner.store.pageSize * this.rowHeight) - centerEl.clientHeight);
106576         calcScrollTop = (scrollTop % ((owner.store.pageSize * this.rowHeight) + 1));
106577         if (useMax) {
106578             calcScrollTop = maxScrollTop;
106579         }
106580         if (calcScrollTop > maxScrollTop) {
106581             //Ext.Error.raise("Calculated scrollTop was larger than maxScrollTop");
106582             return;
106583             // calcScrollTop = maxScrollTop;
106584         }
106585         for (; i < len; i++) {
106586             items[i].el.dom.scrollTop = calcScrollTop;
106587         }
106588     }
106589 });
106590
106591
106592 /**
106593  * @class Ext.panel.Table
106594  * @extends Ext.panel.Panel
106595  * @xtype tablepanel
106596  * @private
106597  * @author Nicolas Ferrero
106598  * TablePanel is a private class and the basis of both TreePanel and GridPanel.
106599  *
106600  * TablePanel aggregates:
106601  *
106602  *  - a Selection Model
106603  *  - a View
106604  *  - a Store
106605  *  - Scrollers
106606  *  - Ext.grid.header.Container
106607  *
106608  */
106609 Ext.define('Ext.panel.Table', {
106610     extend: 'Ext.panel.Panel',
106611
106612     alias: 'widget.tablepanel',
106613
106614     uses: [
106615         'Ext.selection.RowModel',
106616         'Ext.grid.Scroller',
106617         'Ext.grid.header.Container',
106618         'Ext.grid.Lockable'
106619     ],
106620
106621     cls: Ext.baseCSSPrefix + 'grid',
106622     extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
106623
106624     layout: 'fit',
106625     /**
106626      * Boolean to indicate that a view has been injected into the panel.
106627      * @property hasView
106628      */
106629     hasView: false,
106630
106631     // each panel should dictate what viewType and selType to use
106632     viewType: null,
106633     selType: 'rowmodel',
106634
106635     /**
106636      * @cfg {Number} scrollDelta
106637      * Number of pixels to scroll when scrolling with mousewheel.
106638      * Defaults to 40.
106639      */
106640     scrollDelta: 40,
106641
106642     /**
106643      * @cfg {String/Boolean} scroll
106644      * Valid values are 'both', 'horizontal' or 'vertical'. true implies 'both'. false implies 'none'.
106645      * Defaults to true.
106646      */
106647     scroll: true,
106648
106649     /**
106650      * @cfg {Array} columns
106651      * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this grid. Each
106652      * column definition provides the header text for the column, and a definition of where the data for that column comes from.
106653      */
106654
106655     /**
106656      * @cfg {Boolean} forceFit
106657      * Specify as <code>true</code> to force the columns to fit into the available width. Headers are first sized according to configuration, whether that be
106658      * a specific width, or flex. Then they are all proportionally changed in width so that the entire content width is used..
106659      */
106660
106661     /**
106662      * @cfg {Boolean} hideHeaders
106663      * Specify as <code>true</code> to hide the headers.
106664      */
106665
106666     /**
106667      * @cfg {Boolean} sortableColumns
106668      * Defaults to true. Set to false to disable column sorting via clicking the
106669      * header and via the Sorting menu items.
106670      */
106671     sortableColumns: true,
106672
106673     verticalScrollDock: 'right',
106674     verticalScrollerType: 'gridscroller',
106675
106676     horizontalScrollerPresentCls: Ext.baseCSSPrefix + 'horizontal-scroller-present',
106677     verticalScrollerPresentCls: Ext.baseCSSPrefix + 'vertical-scroller-present',
106678
106679     // private property used to determine where to go down to find views
106680     // this is here to support locking.
106681     scrollerOwner: true,
106682
106683     invalidateScrollerOnRefresh: true,
106684     
106685     enableColumnMove: true,
106686     enableColumnResize: true,
106687
106688
106689     initComponent: function() {
106690         if (!this.viewType) {
106691             Ext.Error.raise("You must specify a viewType config.");
106692         }
106693         if (!this.store) {
106694             Ext.Error.raise("You must specify a store config");
106695         }
106696         if (this.headers) {
106697             Ext.Error.raise("The headers config is not supported. Please specify columns instead.");
106698         }
106699
106700         var me          = this,
106701             scroll      = me.scroll,
106702             vertical    = false,
106703             horizontal  = false,
106704             headerCtCfg = me.columns || me.colModel,
106705             i           = 0,
106706             view,
106707             border = me.border;
106708
106709         // Set our determinScrollbars method to reference a buffered call to determinScrollbars which fires on a 30ms buffer.
106710         me.determineScrollbars = Ext.Function.createBuffered(me.determineScrollbars, 30);
106711         me.invalidateScroller = Ext.Function.createBuffered(me.invalidateScroller, 30);
106712         me.injectView = Ext.Function.createBuffered(me.injectView, 30);
106713
106714         if (me.hideHeaders) {
106715             border = false;
106716         }
106717
106718         // The columns/colModel config may be either a fully instantiated HeaderContainer, or an array of Column definitions, or a config object of a HeaderContainer
106719         // Either way, we extract a columns property referencing an array of Column definitions.
106720         if (headerCtCfg instanceof Ext.grid.header.Container) {
106721             me.headerCt = headerCtCfg;
106722             me.headerCt.border = border;
106723             me.columns = me.headerCt.items.items;
106724         } else {
106725             if (Ext.isArray(headerCtCfg)) {
106726                 headerCtCfg = {
106727                     items: headerCtCfg,
106728                     border: border
106729                 };
106730             }
106731             Ext.apply(headerCtCfg, {
106732                 forceFit: me.forceFit,
106733                 sortable: me.sortableColumns,
106734                 enableColumnMove: me.enableColumnMove,
106735                 enableColumnResize: me.enableColumnResize,
106736                 border:  border
106737             });
106738             me.columns = headerCtCfg.items;
106739
106740              // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a
106741              // special view will be injected by the Ext.grid.Lockable mixin, so no processing of .
106742              if (Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) {
106743                  me.self.mixin('lockable', Ext.grid.Lockable);
106744                  me.injectLockable();
106745              }
106746         }
106747
106748         me.store = Ext.data.StoreManager.lookup(me.store);
106749         me.addEvents(
106750             /**
106751              * @event scrollerhide
106752              * Fires when a scroller is hidden
106753              * @param {Ext.grid.Scroller} scroller
106754              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
106755              */
106756             'scrollerhide',
106757             /**
106758              * @event scrollershow
106759              * Fires when a scroller is shown
106760              * @param {Ext.grid.Scroller} scroller
106761              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
106762              */
106763             'scrollershow'
106764         );
106765
106766         me.bodyCls = me.bodyCls || '';
106767         me.bodyCls += (' ' + me.extraBodyCls);
106768
106769         // autoScroll is not a valid configuration
106770         delete me.autoScroll;
106771
106772         // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property)
106773         // 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.
106774         if (!me.hasView) {
106775
106776             // If we were not configured with a ready-made headerCt (either by direct config with a headerCt property, or by passing
106777             // a HeaderContainer instance as the 'columns' property, then go ahead and create one from the config object created above.
106778             if (!me.headerCt) {
106779                 me.headerCt = Ext.create('Ext.grid.header.Container', headerCtCfg);
106780             }
106781
106782             // Extract the array of Column objects
106783             me.columns = me.headerCt.items.items;
106784
106785             if (me.hideHeaders) {
106786                 me.headerCt.height = 0;
106787                 me.headerCt.border = false;
106788                 me.headerCt.addCls(Ext.baseCSSPrefix + 'grid-header-ct-hidden');
106789                 me.addCls(Ext.baseCSSPrefix + 'grid-header-hidden');
106790                 // IE Quirks Mode fix
106791                 // If hidden configuration option was used, several layout calculations will be bypassed.
106792                 if (Ext.isIEQuirks) {
106793                     me.headerCt.style = {
106794                         display: 'none'
106795                     };
106796                 }
106797             }
106798
106799             // turn both on.
106800             if (scroll === true || scroll === 'both') {
106801                 vertical = horizontal = true;
106802             } else if (scroll === 'horizontal') {
106803                 horizontal = true;
106804             } else if (scroll === 'vertical') {
106805                 vertical = true;
106806             // All other values become 'none' or false.
106807             } else {
106808                 me.headerCt.availableSpaceOffset = 0;
106809             }
106810
106811             if (vertical) {
106812                 me.verticalScroller = me.verticalScroller || {};
106813                 Ext.applyIf(me.verticalScroller, {
106814                     dock: me.verticalScrollDock,
106815                     xtype: me.verticalScrollerType,
106816                     store: me.store
106817                 });
106818                 me.verticalScroller = Ext.ComponentManager.create(me.verticalScroller);
106819                 me.mon(me.verticalScroller, {
106820                     bodyscroll: me.onVerticalScroll,
106821                     scope: me
106822                 });
106823             }
106824
106825             if (horizontal) {
106826                 me.horizontalScroller = Ext.ComponentManager.create({
106827                     xtype: 'gridscroller',
106828                     section: me,
106829                     dock: 'bottom',
106830                     store: me.store
106831                 });
106832                 me.mon(me.horizontalScroller, {
106833                     bodyscroll: me.onHorizontalScroll,
106834                     scope: me
106835                 });
106836             }
106837
106838             me.headerCt.on('columnresize', me.onHeaderResize, me);
106839             me.relayEvents(me.headerCt, ['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange']);
106840             me.features = me.features || [];
106841             me.dockedItems = me.dockedItems || [];
106842             me.dockedItems.unshift(me.headerCt);
106843             me.viewConfig = me.viewConfig || {};
106844             me.viewConfig.invalidateScrollerOnRefresh = me.invalidateScrollerOnRefresh;
106845
106846             // AbstractDataView will look up a Store configured as an object
106847             // getView converts viewConfig into a View instance
106848             view = me.getView();
106849
106850             if (view) {
106851                 me.mon(view.store, {
106852                     load: me.onStoreLoad,
106853                     scope: me
106854                 });
106855                 me.mon(view, {
106856                     refresh: {
106857                         fn: this.onViewRefresh,
106858                         scope: me,
106859                         buffer: 50
106860                     },
106861                     itemupdate: me.onViewItemUpdate,
106862                     scope: me
106863                 });
106864                 this.relayEvents(view, [
106865                     /**
106866                      * @event beforeitemmousedown
106867                      * Fires before the mousedown event on an item is processed. Returns false to cancel the default action.
106868                      * @param {Ext.view.View} this
106869                      * @param {Ext.data.Model} record The record that belongs to the item
106870                      * @param {HTMLElement} item The item's element
106871                      * @param {Number} index The item's index
106872                      * @param {Ext.EventObject} e The raw event object
106873                      */
106874                     'beforeitemmousedown',
106875                     /**
106876                      * @event beforeitemmouseup
106877                      * Fires before the mouseup event on an item is processed. Returns false to cancel the default action.
106878                      * @param {Ext.view.View} this
106879                      * @param {Ext.data.Model} record The record that belongs to the item
106880                      * @param {HTMLElement} item The item's element
106881                      * @param {Number} index The item's index
106882                      * @param {Ext.EventObject} e The raw event object
106883                      */
106884                     'beforeitemmouseup',
106885                     /**
106886                      * @event beforeitemmouseenter
106887                      * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action.
106888                      * @param {Ext.view.View} this
106889                      * @param {Ext.data.Model} record The record that belongs to the item
106890                      * @param {HTMLElement} item The item's element
106891                      * @param {Number} index The item's index
106892                      * @param {Ext.EventObject} e The raw event object
106893                      */
106894                     'beforeitemmouseenter',
106895                     /**
106896                      * @event beforeitemmouseleave
106897                      * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action.
106898                      * @param {Ext.view.View} this
106899                      * @param {Ext.data.Model} record The record that belongs to the item
106900                      * @param {HTMLElement} item The item's element
106901                      * @param {Number} index The item's index
106902                      * @param {Ext.EventObject} e The raw event object
106903                      */
106904                     'beforeitemmouseleave',
106905                     /**
106906                      * @event beforeitemclick
106907                      * Fires before the click event on an item is processed. Returns false to cancel the default action.
106908                      * @param {Ext.view.View} this
106909                      * @param {Ext.data.Model} record The record that belongs to the item
106910                      * @param {HTMLElement} item The item's element
106911                      * @param {Number} index The item's index
106912                      * @param {Ext.EventObject} e The raw event object
106913                      */
106914                     'beforeitemclick',
106915                     /**
106916                      * @event beforeitemdblclick
106917                      * Fires before the dblclick event on an item is processed. Returns false to cancel the default action.
106918                      * @param {Ext.view.View} this
106919                      * @param {Ext.data.Model} record The record that belongs to the item
106920                      * @param {HTMLElement} item The item's element
106921                      * @param {Number} index The item's index
106922                      * @param {Ext.EventObject} e The raw event object
106923                      */
106924                     'beforeitemdblclick',
106925                     /**
106926                      * @event beforeitemcontextmenu
106927                      * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action.
106928                      * @param {Ext.view.View} this
106929                      * @param {Ext.data.Model} record The record that belongs to the item
106930                      * @param {HTMLElement} item The item's element
106931                      * @param {Number} index The item's index
106932                      * @param {Ext.EventObject} e The raw event object
106933                      */
106934                     'beforeitemcontextmenu',
106935                     /**
106936                      * @event itemmousedown
106937                      * Fires when there is a mouse down on an item
106938                      * @param {Ext.view.View} this
106939                      * @param {Ext.data.Model} record The record that belongs to the item
106940                      * @param {HTMLElement} item The item's element
106941                      * @param {Number} index The item's index
106942                      * @param {Ext.EventObject} e The raw event object
106943                      */
106944                     'itemmousedown',
106945                     /**
106946                      * @event itemmouseup
106947                      * Fires when there is a mouse up on an item
106948                      * @param {Ext.view.View} this
106949                      * @param {Ext.data.Model} record The record that belongs to the item
106950                      * @param {HTMLElement} item The item's element
106951                      * @param {Number} index The item's index
106952                      * @param {Ext.EventObject} e The raw event object
106953                      */
106954                     'itemmouseup',
106955                     /**
106956                      * @event itemmouseenter
106957                      * Fires when the mouse enters an item.
106958                      * @param {Ext.view.View} this
106959                      * @param {Ext.data.Model} record The record that belongs to the item
106960                      * @param {HTMLElement} item The item's element
106961                      * @param {Number} index The item's index
106962                      * @param {Ext.EventObject} e The raw event object
106963                      */
106964                     'itemmouseenter',
106965                     /**
106966                      * @event itemmouseleave
106967                      * Fires when the mouse leaves an item.
106968                      * @param {Ext.view.View} this
106969                      * @param {Ext.data.Model} record The record that belongs to the item
106970                      * @param {HTMLElement} item The item's element
106971                      * @param {Number} index The item's index
106972                      * @param {Ext.EventObject} e The raw event object
106973                      */
106974                     'itemmouseleave',
106975                     /**
106976                      * @event itemclick
106977                      * Fires when an item is clicked.
106978                      * @param {Ext.view.View} this
106979                      * @param {Ext.data.Model} record The record that belongs to the item
106980                      * @param {HTMLElement} item The item's element
106981                      * @param {Number} index The item's index
106982                      * @param {Ext.EventObject} e The raw event object
106983                      */
106984                     'itemclick',
106985                     /**
106986                      * @event itemdblclick
106987                      * Fires when an item is double clicked.
106988                      * @param {Ext.view.View} this
106989                      * @param {Ext.data.Model} record The record that belongs to the item
106990                      * @param {HTMLElement} item The item's element
106991                      * @param {Number} index The item's index
106992                      * @param {Ext.EventObject} e The raw event object
106993                      */
106994                     'itemdblclick',
106995                     /**
106996                      * @event itemcontextmenu
106997                      * Fires when an item is right clicked.
106998                      * @param {Ext.view.View} this
106999                      * @param {Ext.data.Model} record The record that belongs to the item
107000                      * @param {HTMLElement} item The item's element
107001                      * @param {Number} index The item's index
107002                      * @param {Ext.EventObject} e The raw event object
107003                      */
107004                     'itemcontextmenu',
107005                     /**
107006                      * @event beforecontainermousedown
107007                      * Fires before the mousedown event on the container is processed. Returns false to cancel the default action.
107008                      * @param {Ext.view.View} this
107009                      * @param {Ext.EventObject} e The raw event object
107010                      */
107011                     'beforecontainermousedown',
107012                     /**
107013                      * @event beforecontainermouseup
107014                      * Fires before the mouseup event on the container is processed. Returns false to cancel the default action.
107015                      * @param {Ext.view.View} this
107016                      * @param {Ext.EventObject} e The raw event object
107017                      */
107018                     'beforecontainermouseup',
107019                     /**
107020                      * @event beforecontainermouseover
107021                      * Fires before the mouseover event on the container is processed. Returns false to cancel the default action.
107022                      * @param {Ext.view.View} this
107023                      * @param {Ext.EventObject} e The raw event object
107024                      */
107025                     'beforecontainermouseover',
107026                     /**
107027                      * @event beforecontainermouseout
107028                      * Fires before the mouseout event on the container is processed. Returns false to cancel the default action.
107029                      * @param {Ext.view.View} this
107030                      * @param {Ext.EventObject} e The raw event object
107031                      */
107032                     'beforecontainermouseout',
107033                     /**
107034                      * @event beforecontainerclick
107035                      * Fires before the click event on the container is processed. Returns false to cancel the default action.
107036                      * @param {Ext.view.View} this
107037                      * @param {Ext.EventObject} e The raw event object
107038                      */
107039                     'beforecontainerclick',
107040                     /**
107041                      * @event beforecontainerdblclick
107042                      * Fires before the dblclick event on the container is processed. Returns false to cancel the default action.
107043                      * @param {Ext.view.View} this
107044                      * @param {Ext.EventObject} e The raw event object
107045                      */
107046                     'beforecontainerdblclick',
107047                     /**
107048                      * @event beforecontainercontextmenu
107049                      * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action.
107050                      * @param {Ext.view.View} this
107051                      * @param {Ext.EventObject} e The raw event object
107052                      */
107053                     'beforecontainercontextmenu',
107054                     /**
107055                      * @event containermouseup
107056                      * Fires when there is a mouse up on the container
107057                      * @param {Ext.view.View} this
107058                      * @param {Ext.EventObject} e The raw event object
107059                      */
107060                     'containermouseup',
107061                     /**
107062                      * @event containermouseover
107063                      * Fires when you move the mouse over the container.
107064                      * @param {Ext.view.View} this
107065                      * @param {Ext.EventObject} e The raw event object
107066                      */
107067                     'containermouseover',
107068                     /**
107069                      * @event containermouseout
107070                      * Fires when you move the mouse out of the container.
107071                      * @param {Ext.view.View} this
107072                      * @param {Ext.EventObject} e The raw event object
107073                      */
107074                     'containermouseout',
107075                     /**
107076                      * @event containerclick
107077                      * Fires when the container is clicked.
107078                      * @param {Ext.view.View} this
107079                      * @param {Ext.EventObject} e The raw event object
107080                      */
107081                     'containerclick',
107082                     /**
107083                      * @event containerdblclick
107084                      * Fires when the container is double clicked.
107085                      * @param {Ext.view.View} this
107086                      * @param {Ext.EventObject} e The raw event object
107087                      */
107088                     'containerdblclick',
107089                     /**
107090                      * @event containercontextmenu
107091                      * Fires when the container is right clicked.
107092                      * @param {Ext.view.View} this
107093                      * @param {Ext.EventObject} e The raw event object
107094                      */
107095                     'containercontextmenu',
107096
107097                     /**
107098                      * @event selectionchange
107099                      * Fires when the selected nodes change. Relayed event from the underlying selection model.
107100                      * @param {Ext.view.View} this
107101                      * @param {Array} selections Array of the selected nodes
107102                      */
107103                     'selectionchange',
107104                     /**
107105                      * @event beforeselect
107106                      * Fires before a selection is made. If any handlers return false, the selection is cancelled.
107107                      * @param {Ext.view.View} this
107108                      * @param {HTMLElement} node The node to be selected
107109                      * @param {Array} selections Array of currently selected nodes
107110                      */
107111                     'beforeselect'
107112                 ]);
107113             }
107114         }
107115         me.callParent(arguments);
107116     },
107117
107118     // state management
107119     initStateEvents: function(){
107120         var events = this.stateEvents;
107121         // push on stateEvents if they don't exist
107122         Ext.each(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange'], function(event){
107123             if (Ext.Array.indexOf(events, event)) {
107124                 events.push(event);
107125             }
107126         });
107127         this.callParent();
107128     },
107129
107130     getState: function(){
107131         var state = {
107132             columns: []
107133         },
107134         sorter = this.store.sorters.first();
107135
107136         this.headerCt.items.each(function(header){
107137             state.columns.push({
107138                 id: header.headerId,
107139                 width: header.flex ? undefined : header.width,
107140                 hidden: header.hidden,
107141                 sortable: header.sortable
107142             });
107143         });
107144
107145         if (sorter) {
107146             state.sort = {
107147                 property: sorter.property,
107148                 direction: sorter.direction
107149             };
107150         }
107151         return state;
107152     },
107153
107154     applyState: function(state) {
107155         var headers = state.columns,
107156             length = headers ? headers.length : 0,
107157             headerCt = this.headerCt,
107158             items = headerCt.items,
107159             sorter = state.sort,
107160             store = this.store,
107161             i = 0,
107162             index,
107163             headerState,
107164             header;
107165
107166         for (; i < length; ++i) {
107167             headerState = headers[i];
107168             header = headerCt.down('gridcolumn[headerId=' + headerState.id + ']');
107169             index = items.indexOf(header);
107170             if (i !== index) {
107171                 headerCt.moveHeader(index, i);
107172             }
107173             header.sortable = headerState.sortable;
107174             if (Ext.isDefined(headerState.width)) {
107175                 delete header.flex;
107176                 if (header.rendered) {
107177                     header.setWidth(headerState.width);
107178                 } else {
107179                     header.minWidth = header.width = headerState.width;
107180                 }
107181             }
107182             header.hidden = headerState.hidden;
107183         }
107184
107185         if (sorter) {
107186             if (store.remoteSort) {
107187                 store.sorters.add(Ext.create('Ext.util.Sorter', {
107188                     property: sorter.property,
107189                     direction: sorter.direction
107190                 }));
107191             }
107192             else {
107193                 store.sort(sorter.property, sorter.direction);
107194             }
107195         }
107196     },
107197
107198     /**
107199      * Returns the store associated with this Panel.
107200      * @return {Ext.data.Store} The store
107201      */
107202     getStore: function(){
107203         return this.store;
107204     },
107205
107206     /**
107207      * Gets the view for this panel.
107208      * @return {Ext.view.Table}
107209      */
107210     getView: function() {
107211         var me = this,
107212             sm;
107213
107214         if (!me.view) {
107215             sm = me.getSelectionModel();
107216             me.view = me.createComponent(Ext.apply({}, me.viewConfig, {
107217                 xtype: me.viewType,
107218                 store: me.store,
107219                 headerCt: me.headerCt,
107220                 selModel: sm,
107221                 features: me.features,
107222                 panel: me
107223             }));
107224             me.mon(me.view, {
107225                 uievent: me.processEvent,
107226                 scope: me
107227             });
107228             sm.view = me.view;
107229             me.headerCt.view = me.view;
107230             me.relayEvents(me.view, ['cellclick', 'celldblclick']);
107231         }
107232         return me.view;
107233     },
107234
107235     /**
107236      * @private
107237      * @override
107238      * autoScroll is never valid for all classes which extend TablePanel.
107239      */
107240     setAutoScroll: Ext.emptyFn,
107241
107242     // This method hijacks Ext.view.Table's el scroll method.
107243     // This enables us to keep the virtualized scrollbars in sync
107244     // with the view. It currently does NOT support animation.
107245     elScroll: function(direction, distance, animate) {
107246         var me = this,
107247             scroller;
107248
107249         if (direction === "up" || direction === "left") {
107250             distance = -distance;
107251         }
107252
107253         if (direction === "down" || direction === "up") {
107254             scroller = me.getVerticalScroller();
107255             scroller.scrollByDeltaY(distance);
107256         } else {
107257             scroller = me.getHorizontalScroller();
107258             scroller.scrollByDeltaX(distance);
107259         }
107260     },
107261     
107262     afterLayout: function() {
107263         this.callParent(arguments);
107264         this.injectView();
107265     },
107266     
107267
107268     /**
107269      * @private
107270      * Called after this Component has achieved its correct initial size, after all layouts have done their thing.
107271      * This is so we can add the View only after the initial size is known. This method is buffered 30ms.
107272      */
107273     injectView: function() {
107274         if (!this.hasView && !this.collapsed) {
107275             var me   = this,
107276                 view = me.getView();
107277
107278             me.hasView = true;
107279             me.add(view);
107280
107281             // hijack the view el's scroll method
107282             view.el.scroll = Ext.Function.bind(me.elScroll, me);
107283             // We use to listen to document.body wheel events, but that's a
107284             // little much. We scope just to the view now.
107285             me.mon(view.el, {
107286                 mousewheel: me.onMouseWheel,
107287                 scope: me
107288             });
107289         }
107290     },
107291
107292     afterExpand: function() {
107293         this.callParent(arguments);
107294         if (!this.hasView) {
107295             this.injectView();
107296         }
107297     },
107298
107299     /**
107300      * @private
107301      * Process UI events from the view. Propagate them to whatever internal Components need to process them
107302      * @param {String} type Event type, eg 'click'
107303      * @param {TableView} view TableView Component
107304      * @param {HtmlElement} cell Cell HtmlElement the event took place within
107305      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
107306      * @param {Number} cellIndex Cell index within the row
107307      * @param {EventObject} e Original event
107308      */
107309     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
107310         var me = this,
107311             header;
107312
107313         if (cellIndex !== -1) {
107314             header = me.headerCt.getGridColumns()[cellIndex];
107315             return header.processEvent.apply(header, arguments);
107316         }
107317     },
107318
107319     /**
107320      * Request a recalculation of scrollbars and put them in if they are needed.
107321      */
107322     determineScrollbars: function() {
107323         var me = this,
107324             viewElDom,
107325             centerScrollWidth,
107326             centerClientWidth,
107327             scrollHeight,
107328             clientHeight;
107329
107330         if (!me.collapsed && me.view && me.view.el) {
107331             viewElDom = me.view.el.dom;
107332             //centerScrollWidth = viewElDom.scrollWidth;
107333             centerScrollWidth = me.headerCt.getFullWidth();
107334             /**
107335              * clientWidth often returns 0 in IE resulting in an
107336              * infinity result, here we use offsetWidth bc there are
107337              * no possible scrollbars and we don't care about margins
107338              */
107339             centerClientWidth = viewElDom.offsetWidth;
107340             if (me.verticalScroller && me.verticalScroller.el) {
107341                 scrollHeight = me.verticalScroller.getSizeCalculation().height;
107342             } else {
107343                 scrollHeight = viewElDom.scrollHeight;
107344             }
107345
107346             clientHeight = viewElDom.clientHeight;
107347
107348             me.suspendLayout = true;
107349             me.scrollbarChanged = false;
107350             if (!me.collapsed && scrollHeight > clientHeight) {
107351                 me.showVerticalScroller();
107352             } else {
107353                 me.hideVerticalScroller();
107354             }
107355
107356             if (!me.collapsed && centerScrollWidth > (centerClientWidth + Ext.getScrollBarWidth() - 2)) {
107357                 me.showHorizontalScroller();
107358             } else {
107359                 me.hideHorizontalScroller();
107360             }
107361             me.suspendLayout = false;
107362             if (me.scrollbarChanged) {
107363                 me.doComponentLayout();
107364             }
107365         }
107366     },
107367
107368     onHeaderResize: function() {
107369         if (this.view && this.view.rendered) {
107370             this.determineScrollbars();
107371             this.invalidateScroller();
107372         }
107373     },
107374
107375     /**
107376      * Hide the verticalScroller and remove the horizontalScrollerPresentCls.
107377      */
107378     hideHorizontalScroller: function() {
107379         var me = this;
107380
107381         if (me.horizontalScroller && me.horizontalScroller.ownerCt === me) {
107382             me.scrollbarChanged = true;
107383             me.verticalScroller.offsets.bottom = 0;
107384             me.removeDocked(me.horizontalScroller, false);
107385             me.removeCls(me.horizontalScrollerPresentCls);
107386             me.fireEvent('scrollerhide', me.horizontalScroller, 'horizontal');
107387         }
107388
107389     },
107390
107391     /**
107392      * Show the horizontalScroller and add the horizontalScrollerPresentCls.
107393      */
107394     showHorizontalScroller: function() {
107395         var me = this;
107396
107397         if (me.verticalScroller) {
107398             me.verticalScroller.offsets.bottom = Ext.getScrollBarWidth() - 2;
107399         }
107400         if (me.horizontalScroller && me.horizontalScroller.ownerCt !== me) {
107401             me.scrollbarChanged = true;
107402             me.addDocked(me.horizontalScroller);
107403             me.addCls(me.horizontalScrollerPresentCls);
107404             me.fireEvent('scrollershow', me.horizontalScroller, 'horizontal');
107405         }
107406     },
107407
107408     /**
107409      * Hide the verticalScroller and remove the verticalScrollerPresentCls.
107410      */
107411     hideVerticalScroller: function() {
107412         var me = this,
107413             headerCt = me.headerCt;
107414
107415         // only trigger a layout when reserveOffset is changing
107416         if (headerCt && headerCt.layout.reserveOffset) {
107417             headerCt.layout.reserveOffset = false;
107418             headerCt.doLayout();
107419         }
107420         if (me.verticalScroller && me.verticalScroller.ownerCt === me) {
107421             me.scrollbarChanged = true;
107422             me.removeDocked(me.verticalScroller, false);
107423             me.removeCls(me.verticalScrollerPresentCls);
107424             me.fireEvent('scrollerhide', me.verticalScroller, 'vertical');
107425         }
107426     },
107427
107428     /**
107429      * Show the verticalScroller and add the verticalScrollerPresentCls.
107430      */
107431     showVerticalScroller: function() {
107432         var me = this,
107433             headerCt = me.headerCt;
107434
107435         // only trigger a layout when reserveOffset is changing
107436         if (headerCt && !headerCt.layout.reserveOffset) {
107437             headerCt.layout.reserveOffset = true;
107438             headerCt.doLayout();
107439         }
107440         if (me.verticalScroller && me.verticalScroller.ownerCt !== me) {
107441             me.scrollbarChanged = true;
107442             me.addDocked(me.verticalScroller);
107443             me.addCls(me.verticalScrollerPresentCls);
107444             me.fireEvent('scrollershow', me.verticalScroller, 'vertical');
107445         }
107446     },
107447
107448     /**
107449      * Invalides scrollers that are present and forces a recalculation.
107450      * (Not related to showing/hiding the scrollers)
107451      */
107452     invalidateScroller: function() {
107453         var me = this,
107454             vScroll = me.verticalScroller,
107455             hScroll = me.horizontalScroller;
107456
107457         if (vScroll) {
107458             vScroll.invalidate();
107459         }
107460         if (hScroll) {
107461             hScroll.invalidate();
107462         }
107463     },
107464
107465     // refresh the view when a header moves
107466     onHeaderMove: function(headerCt, header, fromIdx, toIdx) {
107467         this.view.refresh();
107468     },
107469
107470     // Section onHeaderHide is invoked after view.
107471     onHeaderHide: function(headerCt, header) {
107472         this.invalidateScroller();
107473     },
107474
107475     onHeaderShow: function(headerCt, header) {
107476         this.invalidateScroller();
107477     },
107478
107479     getVerticalScroller: function() {
107480         return this.getScrollerOwner().down('gridscroller[dock=' + this.verticalScrollDock + ']');
107481     },
107482
107483     getHorizontalScroller: function() {
107484         return this.getScrollerOwner().down('gridscroller[dock=bottom]');
107485     },
107486
107487     onMouseWheel: function(e) {
107488         var me = this,
107489             browserEvent = e.browserEvent,
107490             vertScroller = me.getVerticalScroller(),
107491             horizScroller = me.getHorizontalScroller(),
107492             scrollDelta = me.scrollDelta,
107493             deltaY, deltaX,
107494             vertScrollerEl, horizScrollerEl,
107495             vertScrollerElDom, horizScrollerElDom,
107496             horizontalCanScrollLeft, horizontalCanScrollRight,
107497             verticalCanScrollDown, verticalCanScrollUp;
107498
107499         // calculate whether or not both scrollbars can scroll right/left and up/down
107500         if (horizScroller) {
107501             horizScrollerEl = horizScroller.el;
107502             if (horizScrollerEl) {
107503                 horizScrollerElDom = horizScrollerEl.dom;
107504                 horizontalCanScrollRight = horizScrollerElDom.scrollLeft !== horizScrollerElDom.scrollWidth - horizScrollerElDom.clientWidth;
107505                 horizontalCanScrollLeft  = horizScrollerElDom.scrollLeft !== 0;
107506             }
107507         }
107508         if (vertScroller) {
107509             vertScrollerEl = vertScroller.el;
107510             if (vertScrollerEl) {
107511                 vertScrollerElDom = vertScrollerEl.dom;
107512                 verticalCanScrollDown = vertScrollerElDom.scrollTop !== vertScrollerElDom.scrollHeight - vertScrollerElDom.clientHeight;
107513                 verticalCanScrollUp   = vertScrollerElDom.scrollTop !== 0;
107514             }
107515         }
107516
107517         // Webkit Horizontal Axis
107518         if (browserEvent.wheelDeltaX || browserEvent.wheelDeltaY) {        
107519             deltaX = -browserEvent.wheelDeltaX / 120 * scrollDelta / 3;
107520             deltaY = -browserEvent.wheelDeltaY / 120 * scrollDelta / 3;
107521         } else {
107522             // Gecko Horizontal Axis
107523             if (browserEvent.axis && browserEvent.axis === 1) {
107524                 deltaX = -(scrollDelta * e.getWheelDelta()) / 3;
107525             } else {
107526                 deltaY = -(scrollDelta * e.getWheelDelta() / 3);
107527             }
107528         }
107529         
107530         if (horizScroller) {
107531             if ((deltaX < 0 && horizontalCanScrollLeft) || (deltaX > 0 && horizontalCanScrollRight)) {
107532                 e.stopEvent();
107533                 horizScroller.scrollByDeltaX(deltaX);
107534             }
107535         }
107536         if (vertScroller) {
107537             if ((deltaY < 0 && verticalCanScrollUp) || (deltaY > 0 && verticalCanScrollDown)) {
107538                 e.stopEvent();
107539                 vertScroller.scrollByDeltaY(deltaY);    
107540             }
107541         }
107542     },
107543
107544     /**
107545      * @private
107546      * Determine and invalidate scrollers on view refresh
107547      */
107548     onViewRefresh: function() {
107549         if (Ext.isIE) {
107550             this.syncCellHeight();
107551         }
107552         this.determineScrollbars();
107553         if (this.invalidateScrollerOnRefresh) {
107554             this.invalidateScroller();
107555         }
107556     },
107557
107558     onViewItemUpdate: function(record, index, tr) {
107559         if (Ext.isIE) {
107560             this.syncCellHeight([tr]);
107561         }
107562     },
107563
107564     // BrowserBug: IE will not stretch the td to fit the height of the entire
107565     // tr, so manually sync cellheights on refresh and when an item has been
107566     // updated.
107567     syncCellHeight: function(trs) {
107568         var me    = this,
107569             i     = 0,
107570             tds,
107571             j, tdsLn,
107572             tr, td,
107573             trsLn,
107574             rowHeights = [],
107575             cellHeights,
107576             cellClsSelector = ('.' + Ext.baseCSSPrefix + 'grid-cell');
107577
107578         trs   = trs || me.view.getNodes();
107579         
107580         trsLn = trs.length;
107581         // Reading loop
107582         for (; i < trsLn; i++) {
107583             tr = trs[i];
107584             tds = Ext.fly(tr).query(cellClsSelector);
107585             tdsLn = tds.length;
107586             cellHeights = [];
107587             for (j = 0; j < tdsLn; j++) {
107588                 td = tds[j];
107589                 cellHeights.push(td.clientHeight);
107590             }
107591             rowHeights.push(Ext.Array.max(cellHeights));
107592         }
107593
107594         // Setting loop
107595         for (i = 0; i < trsLn; i++) {
107596             tr = trs[i];
107597             tdsLn = tr.childNodes.length;
107598             for (j = 0; j < tdsLn; j++) {
107599                 td = Ext.fly(tr.childNodes[j]);
107600                 if (rowHeights[i]) {
107601                     if (td.is(cellClsSelector)) {
107602                         td.setHeight(rowHeights[i]);
107603                     } else {
107604                         td.down(cellClsSelector).setHeight(rowHeights[i]);
107605                     }
107606                 }
107607                 
107608             }
107609         }
107610     },
107611
107612     /**
107613      * Sets the scrollTop of the TablePanel.
107614      * @param {Number} deltaY
107615      */
107616     setScrollTop: function(top) {
107617         var me               = this,
107618             rootCmp          = me.getScrollerOwner(),
107619             verticalScroller = me.getVerticalScroller();
107620
107621         rootCmp.virtualScrollTop = top;
107622         if (verticalScroller) {
107623             verticalScroller.setScrollTop(top);
107624         }
107625
107626     },
107627
107628     getScrollerOwner: function() {
107629         var rootCmp = this;
107630         if (!this.scrollerOwner) {
107631             rootCmp = this.up('[scrollerOwner]');
107632         }
107633         return rootCmp;
107634     },
107635
107636     /**
107637      * Scrolls the TablePanel by deltaY
107638      * @param {Number} deltaY
107639      */
107640     scrollByDeltaY: function(deltaY) {
107641         var rootCmp = this.getScrollerOwner(),
107642             scrollerRight;
107643         scrollerRight = rootCmp.down('gridscroller[dock=' + this.verticalScrollDock + ']');
107644         if (scrollerRight) {
107645             scrollerRight.scrollByDeltaY(deltaY);
107646         }
107647     },
107648
107649
107650     /**
107651      * Scrolls the TablePanel by deltaX
107652      * @param {Number} deltaY
107653      */
107654     scrollByDeltaX: function(deltaX) {
107655         this.horizontalScroller.scrollByDeltaX(deltaX);
107656     },
107657
107658     /**
107659      * Get left hand side marker for header resizing.
107660      * @private
107661      */
107662     getLhsMarker: function() {
107663         var me = this;
107664
107665         if (!me.lhsMarker) {
107666             me.lhsMarker = Ext.core.DomHelper.append(me.el, {
107667                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
107668             }, true);
107669         }
107670         return me.lhsMarker;
107671     },
107672
107673     /**
107674      * Get right hand side marker for header resizing.
107675      * @private
107676      */
107677     getRhsMarker: function() {
107678         var me = this;
107679
107680         if (!me.rhsMarker) {
107681             me.rhsMarker = Ext.core.DomHelper.append(me.el, {
107682                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
107683             }, true);
107684         }
107685         return me.rhsMarker;
107686     },
107687
107688     /**
107689      * Returns the selection model being used and creates it via the configuration
107690      * if it has not been created already.
107691      * @return {Ext.selection.Model} selModel
107692      */
107693     getSelectionModel: function(){
107694         if (!this.selModel) {
107695             this.selModel = {};
107696         }
107697
107698         var mode = 'SINGLE',
107699             type;
107700         if (this.simpleSelect) {
107701             mode = 'SIMPLE';
107702         } else if (this.multiSelect) {
107703             mode = 'MULTI';
107704         }
107705
107706         Ext.applyIf(this.selModel, {
107707             allowDeselect: this.allowDeselect,
107708             mode: mode
107709         });
107710
107711         if (!this.selModel.events) {
107712             type = this.selModel.selType || this.selType;
107713             this.selModel = Ext.create('selection.' + type, this.selModel);
107714         }
107715
107716         if (!this.selModel.hasRelaySetup) {
107717             this.relayEvents(this.selModel, ['selectionchange', 'select', 'deselect']);
107718             this.selModel.hasRelaySetup = true;
107719         }
107720
107721         // lock the selection model if user
107722         // has disabled selection
107723         if (this.disableSelection) {
107724             this.selModel.locked = true;
107725         }
107726         return this.selModel;
107727     },
107728
107729     onVerticalScroll: function(event, target) {
107730         var owner = this.getScrollerOwner(),
107731             items = owner.query('tableview'),
107732             i = 0,
107733             len = items.length;
107734
107735         for (; i < len; i++) {
107736             items[i].el.dom.scrollTop = target.scrollTop;
107737         }
107738     },
107739
107740     onHorizontalScroll: function(event, target) {
107741         var owner = this.getScrollerOwner(),
107742             items = owner.query('tableview'),
107743             i = 0,
107744             len = items.length,
107745             center,
107746             centerEl,
107747             centerScrollWidth,
107748             centerClientWidth,
107749             width;
107750
107751         center = items[1] || items[0];
107752         centerEl = center.el.dom;
107753         centerScrollWidth = centerEl.scrollWidth;
107754         centerClientWidth = centerEl.offsetWidth;
107755         width = this.horizontalScroller.getWidth();
107756
107757         centerEl.scrollLeft = target.scrollLeft;
107758         this.headerCt.el.dom.scrollLeft = target.scrollLeft;
107759     },
107760
107761     // template method meant to be overriden
107762     onStoreLoad: Ext.emptyFn,
107763
107764     getEditorParent: function() {
107765         return this.body;
107766     },
107767
107768     bindStore: function(store) {
107769         var me = this;
107770         me.store = store;
107771         me.getView().bindStore(store);
107772     },
107773
107774     reconfigure: function(store, columns) {
107775         var me = this;
107776
107777         if (me.lockable) {
107778             me.reconfigureLockable(store, columns);
107779             return;
107780         }
107781
107782         if (columns) {
107783             me.headerCt.removeAll();
107784             me.headerCt.add(columns);
107785         }
107786         if (store) {
107787             store = Ext.StoreManager.lookup(store);
107788             me.bindStore(store);
107789         } else {
107790             me.getView().refresh();
107791         }
107792     },
107793     
107794     afterComponentLayout: function() {
107795         var me = this;
107796         me.callParent(arguments);
107797         me.determineScrollbars();
107798         me.invalidateScroller();
107799     }
107800 });
107801 /**
107802  * @class Ext.view.Table
107803  * @extends Ext.view.View
107804
107805 This class encapsulates the user interface for a tabular data set.
107806 It acts as a centralized manager for controlling the various interface
107807 elements of the view. This includes handling events, such as row and cell
107808 level based DOM events. It also reacts to events from the underlying {@link Ext.selection.Model}
107809 to provide visual feedback to the user. 
107810
107811 This class does not provide ways to manipulate the underlying data of the configured
107812 {@link Ext.data.Store}.
107813
107814 This is the base class for both {@link Ext.grid.View} and {@link Ext.tree.View} and is not
107815 to be used directly.
107816
107817  * @markdown
107818  * @abstract
107819  * @xtype tableview
107820  * @author Nicolas Ferrero
107821  */
107822 Ext.define('Ext.view.Table', {
107823     extend: 'Ext.view.View',
107824     alias: 'widget.tableview',
107825     uses: [
107826         'Ext.view.TableChunker',
107827         'Ext.util.DelayedTask',
107828         'Ext.util.MixedCollection'
107829     ],
107830
107831     cls: Ext.baseCSSPrefix + 'grid-view',
107832
107833     // row
107834     itemSelector: '.' + Ext.baseCSSPrefix + 'grid-row',
107835     // cell
107836     cellSelector: '.' + Ext.baseCSSPrefix + 'grid-cell',
107837
107838     selectedItemCls: Ext.baseCSSPrefix + 'grid-row-selected',
107839     selectedCellCls: Ext.baseCSSPrefix + 'grid-cell-selected',
107840     focusedItemCls: Ext.baseCSSPrefix + 'grid-row-focused',
107841     overItemCls: Ext.baseCSSPrefix + 'grid-row-over',
107842     altRowCls:   Ext.baseCSSPrefix + 'grid-row-alt',
107843     rowClsRe: /(?:^|\s*)grid-row-(first|last|alt)(?:\s+|$)/g,
107844     cellRe: new RegExp('x-grid-cell-([^\\s]+) ', ''),
107845
107846     // cfg docs inherited
107847     trackOver: true,
107848
107849     /**
107850      * Override this function to apply custom CSS classes to rows during rendering.  You can also supply custom
107851      * parameters to the row template for the current row to customize how it is rendered using the <b>rowParams</b>
107852      * parameter.  This function should return the CSS class name (or empty string '' for none) that will be added
107853      * to the row's wrapping div.  To apply multiple class names, simply return them space-delimited within the string
107854      * (e.g., 'my-class another-class'). Example usage:
107855     <pre><code>
107856 viewConfig: {
107857     forceFit: true,
107858     showPreview: true, // custom property
107859     enableRowBody: true, // required to create a second, full-width row to show expanded Record data
107860     getRowClass: function(record, rowIndex, rp, ds){ // rp = rowParams
107861         if(this.showPreview){
107862             rp.body = '&lt;p>'+record.data.excerpt+'&lt;/p>';
107863             return 'x-grid3-row-expanded';
107864         }
107865         return 'x-grid3-row-collapsed';
107866     }
107867 },
107868     </code></pre>
107869      * @param {Model} model The {@link Ext.data.Model} corresponding to the current row.
107870      * @param {Number} index The row index.
107871      * @param {Object} rowParams (DEPRECATED) A config object that is passed to the row template during rendering that allows
107872      * customization of various aspects of a grid row.
107873      * <p>If {@link #enableRowBody} is configured <b><tt></tt>true</b>, then the following properties may be set
107874      * by this function, and will be used to render a full-width expansion row below each grid row:</p>
107875      * <ul>
107876      * <li><code>body</code> : String <div class="sub-desc">An HTML fragment to be used as the expansion row's body content (defaults to '').</div></li>
107877      * <li><code>bodyStyle</code> : String <div class="sub-desc">A CSS style specification that will be applied to the expansion row's &lt;tr> element. (defaults to '').</div></li>
107878      * </ul>
107879      * The following property will be passed in, and may be appended to:
107880      * <ul>
107881      * <li><code>tstyle</code> : String <div class="sub-desc">A CSS style specification that willl be applied to the &lt;table> element which encapsulates
107882      * both the standard grid row, and any expansion row.</div></li>
107883      * </ul>
107884      * @param {Store} store The {@link Ext.data.Store} this grid is bound to
107885      * @method getRowClass
107886      * @return {String} a CSS class name to add to the row.
107887      */
107888     getRowClass: null,
107889
107890     initComponent: function() {
107891         var me = this;
107892         
107893         me.scrollState = {};
107894         me.selModel.view = me;
107895         me.headerCt.view = me;
107896         me.initFeatures();
107897         me.tpl = '<div></div>';
107898         me.callParent();
107899         me.mon(me.store, {
107900             load: me.onStoreLoad,
107901             scope: me
107902         });
107903
107904         // this.addEvents(
107905         //     /**
107906         //      * @event rowfocus
107907         //      * @param {Ext.data.Record} record
107908         //      * @param {HTMLElement} row
107909         //      * @param {Number} rowIdx
107910         //      */
107911         //     'rowfocus'
107912         // );
107913     },
107914
107915     // scroll to top of the grid when store loads
107916     onStoreLoad: function(){
107917         var me = this;
107918         
107919         if (me.invalidateScrollerOnRefresh) {
107920             if (Ext.isGecko) {
107921                 if (!me.scrollToTopTask) {
107922                     me.scrollToTopTask = Ext.create('Ext.util.DelayedTask', me.scrollToTop, me);
107923                 }
107924                 me.scrollToTopTask.delay(1);
107925             } else {
107926                 me    .scrollToTop();
107927             }
107928         }
107929     },
107930
107931     // scroll the view to the top
107932     scrollToTop: Ext.emptyFn,
107933     
107934     /**
107935      * Add a listener to the main view element. It will be destroyed with the view.
107936      * @private
107937      */
107938     addElListener: function(eventName, fn, scope){
107939         this.mon(this, eventName, fn, scope, {
107940             element: 'el'
107941         });
107942     },
107943     
107944     /**
107945      * Get the columns used for generating a template via TableChunker.
107946      * See {@link Ext.grid.header.Container#getGridColumns}.
107947      * @private
107948      */
107949     getGridColumns: function() {
107950         return this.headerCt.getGridColumns();    
107951     },
107952     
107953     /**
107954      * Get a leaf level header by index regardless of what the nesting
107955      * structure is.
107956      * @private
107957      * @param {Number} index The index
107958      */
107959     getHeaderAtIndex: function(index) {
107960         return this.headerCt.getHeaderAtIndex(index);
107961     },
107962     
107963     /**
107964      * Get the cell (td) for a particular record and column.
107965      * @param {Ext.data.Model} record
107966      * @param {Ext.grid.column.Colunm} column
107967      * @private
107968      */
107969     getCell: function(record, column) {
107970         var row = this.getNode(record);
107971         return Ext.fly(row).down(column.getCellSelector());
107972     },
107973
107974     /**
107975      * Get a reference to a feature
107976      * @param {String} id The id of the feature
107977      * @return {Ext.grid.feature.Feature} The feature. Undefined if not found
107978      */
107979     getFeature: function(id) {
107980         var features = this.featuresMC;
107981         if (features) {
107982             return features.get(id);
107983         }
107984     },
107985
107986     /**
107987      * Initializes each feature and bind it to this view.
107988      * @private
107989      */
107990     initFeatures: function() {
107991         var me = this,
107992             i = 0,
107993             features,
107994             len;
107995             
107996         me.features = me.features || [];
107997         features = me.features;
107998         len = features.length;
107999
108000         me.featuresMC = Ext.create('Ext.util.MixedCollection');
108001         for (; i < len; i++) {
108002             // ensure feature hasnt already been instantiated
108003             if (!features[i].isFeature) {
108004                 features[i] = Ext.create('feature.' + features[i].ftype, features[i]);
108005             }
108006             // inject a reference to view
108007             features[i].view = me;
108008             me.featuresMC.add(features[i]);
108009         }
108010     },
108011
108012     /**
108013      * Gives features an injection point to attach events to the markup that
108014      * has been created for this view.
108015      * @private
108016      */
108017     attachEventsForFeatures: function() {
108018         var features = this.features,
108019             ln       = features.length,
108020             i        = 0;
108021
108022         for (; i < ln; i++) {
108023             if (features[i].isFeature) {
108024                 features[i].attachEvents();
108025             }
108026         }
108027     },
108028
108029     afterRender: function() {
108030         var me = this;
108031         
108032         me.callParent();
108033         me.mon(me.el, {
108034             scroll: me.fireBodyScroll,
108035             scope: me
108036         });
108037         me.el.unselectable();
108038         me.attachEventsForFeatures();
108039     },
108040
108041     fireBodyScroll: function(e, t) {
108042         this.fireEvent('bodyscroll', e, t);
108043     },
108044
108045     // TODO: Refactor headerCt dependency here to colModel
108046     /**
108047      * Uses the headerCt to transform data from dataIndex keys in a record to
108048      * headerId keys in each header and then run them through each feature to
108049      * get additional data for variables they have injected into the view template.
108050      * @private
108051      */
108052     prepareData: function(data, idx, record) {
108053         var me       = this,
108054             orig     = me.headerCt.prepareData(data, idx, record, me, me.ownerCt),
108055             features = me.features,
108056             ln       = features.length,
108057             i        = 0,
108058             node, feature;
108059
108060         for (; i < ln; i++) {
108061             feature = features[i];
108062             if (feature.isFeature) {
108063                 Ext.apply(orig, feature.getAdditionalData(data, idx, record, orig, me));
108064             }
108065         }
108066
108067         return orig;
108068     },
108069
108070     // TODO: Refactor headerCt dependency here to colModel
108071     collectData: function(records, startIndex) {
108072         var preppedRecords = this.callParent(arguments),
108073             headerCt  = this.headerCt,
108074             fullWidth = headerCt.getFullWidth(),
108075             features  = this.features,
108076             ln = features.length,
108077             o = {
108078                 rows: preppedRecords,
108079                 fullWidth: fullWidth
108080             },
108081             i  = 0,
108082             feature,
108083             j = 0,
108084             jln,
108085             rowParams;
108086
108087         jln = preppedRecords.length;
108088         // process row classes, rowParams has been deprecated and has been moved
108089         // to the individual features that implement the behavior. 
108090         if (this.getRowClass) {
108091             for (; j < jln; j++) {
108092                 rowParams = {};
108093                 preppedRecords[j]['rowCls'] = this.getRowClass(records[j], j, rowParams, this.store);
108094                 if (rowParams.alt) {
108095                     Ext.Error.raise("The getRowClass alt property is no longer supported.");
108096                 }
108097                 if (rowParams.tstyle) {
108098                     Ext.Error.raise("The getRowClass tstyle property is no longer supported.");
108099                 }
108100                 if (rowParams.cells) {
108101                     Ext.Error.raise("The getRowClass cells property is no longer supported.");
108102                 }
108103                 if (rowParams.body) {
108104                     Ext.Error.raise("The getRowClass body property is no longer supported. Use the getAdditionalData method of the rowbody feature.");
108105                 }
108106                 if (rowParams.bodyStyle) {
108107                     Ext.Error.raise("The getRowClass bodyStyle property is no longer supported.");
108108                 }
108109                 if (rowParams.cols) {
108110                     Ext.Error.raise("The getRowClass cols property is no longer supported.");
108111                 }
108112             }
108113         }
108114         // currently only one feature may implement collectData. This is to modify
108115         // what's returned to the view before its rendered
108116         for (; i < ln; i++) {
108117             feature = features[i];
108118             if (feature.isFeature && feature.collectData && !feature.disabled) {
108119                 o = feature.collectData(records, preppedRecords, startIndex, fullWidth, o);
108120                 break;
108121             }
108122         }
108123         return o;
108124     },
108125
108126     // TODO: Refactor header resizing to column resizing
108127     /**
108128      * When a header is resized, setWidth on the individual columns resizer class,
108129      * the top level table, save/restore scroll state, generate a new template and
108130      * restore focus to the grid view's element so that keyboard navigation
108131      * continues to work.
108132      * @private
108133      */
108134     onHeaderResize: function(header, w, suppressFocus) {
108135         var me = this,
108136             el = me.el;
108137         if (el) {
108138             me.saveScrollState();
108139             // Grab the col and set the width, css
108140             // class is generated in TableChunker.
108141             // Select composites because there may be several chunks.
108142             el.select('.' + Ext.baseCSSPrefix + 'grid-col-resizer-'+header.id).setWidth(w);
108143             el.select('.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(me.headerCt.getFullWidth());
108144             me.restoreScrollState();
108145             me.setNewTemplate();
108146             if (!suppressFocus) {
108147                 me.el.focus();
108148             }
108149         }
108150     },
108151
108152     /**
108153      * When a header is shown restore its oldWidth if it was previously hidden.
108154      * @private
108155      */
108156     onHeaderShow: function(headerCt, header, suppressFocus) {
108157         // restore headers that were dynamically hidden
108158         if (header.oldWidth) {
108159             this.onHeaderResize(header, header.oldWidth, suppressFocus);
108160             delete header.oldWidth;
108161         // flexed headers will have a calculated size set
108162         // this additional check has to do with the fact that
108163         // defaults: {width: 100} will fight with a flex value
108164         } else if (header.width && !header.flex) {
108165             this.onHeaderResize(header, header.width, suppressFocus);
108166         }
108167         this.setNewTemplate();
108168     },
108169
108170     /**
108171      * When the header hides treat it as a resize to 0.
108172      * @private
108173      */
108174     onHeaderHide: function(headerCt, header, suppressFocus) {
108175         this.onHeaderResize(header, 0, suppressFocus);
108176     },
108177
108178     /**
108179      * Set a new template based on the current columns displayed in the
108180      * grid.
108181      * @private
108182      */
108183     setNewTemplate: function() {
108184         var me = this,
108185             columns = me.headerCt.getColumnsForTpl(true);
108186             
108187         me.tpl = me.getTableChunker().getTableTpl({
108188             columns: columns,
108189             features: me.features
108190         });
108191     },
108192
108193     /**
108194      * Get the configured chunker or default of Ext.view.TableChunker
108195      */
108196     getTableChunker: function() {
108197         return this.chunker || Ext.view.TableChunker;
108198     },
108199
108200     /**
108201      * Add a CSS Class to a specific row.
108202      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model representing this row
108203      * @param {String} cls
108204      */
108205     addRowCls: function(rowInfo, cls) {
108206         var row = this.getNode(rowInfo);
108207         if (row) {
108208             Ext.fly(row).addCls(cls);
108209         }
108210     },
108211
108212     /**
108213      * Remove a CSS Class from a specific row.
108214      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model representing this row
108215      * @param {String} cls
108216      */
108217     removeRowCls: function(rowInfo, cls) {
108218         var row = this.getNode(rowInfo);
108219         if (row) {
108220             Ext.fly(row).removeCls(cls);
108221         }
108222     },
108223
108224     // GridSelectionModel invokes onRowSelect as selection changes
108225     onRowSelect : function(rowIdx) {
108226         this.addRowCls(rowIdx, this.selectedItemCls);
108227     },
108228
108229     // GridSelectionModel invokes onRowDeselect as selection changes
108230     onRowDeselect : function(rowIdx) {
108231         var me = this;
108232         
108233         me.removeRowCls(rowIdx, me.selectedItemCls);
108234         me.removeRowCls(rowIdx, me.focusedItemCls);
108235     },
108236     
108237     onCellSelect: function(position) {
108238         var cell = this.getCellByPosition(position);
108239         if (cell) {
108240             cell.addCls(this.selectedCellCls);
108241         }
108242     },
108243     
108244     onCellDeselect: function(position) {
108245         var cell = this.getCellByPosition(position);
108246         if (cell) {
108247             cell.removeCls(this.selectedCellCls);
108248         }
108249         
108250     },
108251     
108252     onCellFocus: function(position) {
108253         //var cell = this.getCellByPosition(position);
108254         this.focusCell(position);
108255     },
108256     
108257     getCellByPosition: function(position) {
108258         var row    = position.row,
108259             column = position.column,
108260             store  = this.store,
108261             node   = this.getNode(row),
108262             header = this.headerCt.getHeaderAtIndex(column),
108263             cellSelector,
108264             cell = false;
108265             
108266         if (header && node) {
108267             cellSelector = header.getCellSelector();
108268             cell = Ext.fly(node).down(cellSelector);
108269         }
108270         return cell;
108271     },
108272
108273     // GridSelectionModel invokes onRowFocus to 'highlight'
108274     // the last row focused
108275     onRowFocus: function(rowIdx, highlight, supressFocus) {
108276         var me = this,
108277             row = me.getNode(rowIdx);
108278
108279         if (highlight) {
108280             me.addRowCls(rowIdx, me.focusedItemCls);
108281             if (!supressFocus) {
108282                 me.focusRow(rowIdx);
108283             }
108284             //this.el.dom.setAttribute('aria-activedescendant', row.id);
108285         } else {
108286             me.removeRowCls(rowIdx, me.focusedItemCls);
108287         }
108288     },
108289
108290     /**
108291      * Focus a particular row and bring it into view. Will fire the rowfocus event.
108292      * @cfg {Mixed} An HTMLElement template node, index of a template node, the
108293      * id of a template node or the record associated with the node.
108294      */
108295     focusRow: function(rowIdx) {
108296         var me         = this,
108297             row        = me.getNode(rowIdx),
108298             el         = me.el,
108299             adjustment = 0,
108300             panel      = me.ownerCt,
108301             rowRegion,
108302             elRegion,
108303             record;
108304             
108305         if (row && el) {
108306             elRegion  = el.getRegion();
108307             rowRegion = Ext.fly(row).getRegion();
108308             // row is above
108309             if (rowRegion.top < elRegion.top) {
108310                 adjustment = rowRegion.top - elRegion.top;
108311             // row is below
108312             } else if (rowRegion.bottom > elRegion.bottom) {
108313                 adjustment = rowRegion.bottom - elRegion.bottom;
108314             }
108315             record = me.getRecord(row);
108316             rowIdx = me.store.indexOf(record);
108317
108318             if (adjustment) {
108319                 // scroll the grid itself, so that all gridview's update.
108320                 panel.scrollByDeltaY(adjustment);
108321             }
108322             me.fireEvent('rowfocus', record, row, rowIdx);
108323         }
108324     },
108325
108326     focusCell: function(position) {
108327         var me          = this,
108328             cell        = me.getCellByPosition(position),
108329             el          = me.el,
108330             adjustmentY = 0,
108331             adjustmentX = 0,
108332             elRegion    = el.getRegion(),
108333             panel       = me.ownerCt,
108334             cellRegion,
108335             record;
108336
108337         if (cell) {
108338             cellRegion = cell.getRegion();
108339             // cell is above
108340             if (cellRegion.top < elRegion.top) {
108341                 adjustmentY = cellRegion.top - elRegion.top;
108342             // cell is below
108343             } else if (cellRegion.bottom > elRegion.bottom) {
108344                 adjustmentY = cellRegion.bottom - elRegion.bottom;
108345             }
108346
108347             // cell is left
108348             if (cellRegion.left < elRegion.left) {
108349                 adjustmentX = cellRegion.left - elRegion.left;
108350             // cell is right
108351             } else if (cellRegion.right > elRegion.right) {
108352                 adjustmentX = cellRegion.right - elRegion.right;
108353             }
108354
108355             if (adjustmentY) {
108356                 // scroll the grid itself, so that all gridview's update.
108357                 panel.scrollByDeltaY(adjustmentY);
108358             }
108359             if (adjustmentX) {
108360                 panel.scrollByDeltaX(adjustmentX);
108361             }
108362             el.focus();
108363             me.fireEvent('cellfocus', record, cell, position);
108364         }
108365     },
108366
108367     /**
108368      * Scroll by delta. This affects this individual view ONLY and does not
108369      * synchronize across views or scrollers.
108370      * @param {Number} delta
108371      * @param {String} dir (optional) Valid values are scrollTop and scrollLeft. Defaults to scrollTop.
108372      * @private
108373      */
108374     scrollByDelta: function(delta, dir) {
108375         dir = dir || 'scrollTop';
108376         var elDom = this.el.dom;
108377         elDom[dir] = (elDom[dir] += delta);
108378     },
108379
108380     onUpdate: function(ds, index) {
108381         this.callParent(arguments);
108382     },
108383
108384     /**
108385      * Save the scrollState in a private variable.
108386      * Must be used in conjunction with restoreScrollState
108387      */
108388     saveScrollState: function() {
108389         var dom = this.el.dom,
108390             state = this.scrollState;
108391
108392         state.left = dom.scrollLeft;
108393         state.top = dom.scrollTop;
108394     },
108395
108396     /**
108397      * Restore the scrollState.
108398      * Must be used in conjunction with saveScrollState
108399      * @private
108400      */
108401     restoreScrollState: function() {
108402         var dom = this.el.dom,
108403             state = this.scrollState,
108404             headerEl = this.headerCt.el.dom;
108405
108406         headerEl.scrollLeft = dom.scrollLeft = state.left;
108407         dom.scrollTop = state.top;
108408     },
108409
108410     /**
108411      * Refresh the grid view.
108412      * Saves and restores the scroll state, generates a new template, stripes rows
108413      * and invalidates the scrollers.
108414      * @param {Boolean} firstPass This is a private flag for internal use only.
108415      */
108416     refresh: function(firstPass) {
108417         var me = this,
108418             table;
108419
108420         //this.saveScrollState();
108421         me.setNewTemplate();
108422         
108423         me.callParent(arguments);
108424
108425         //this.restoreScrollState();
108426
108427         if (me.rendered && !firstPass) {
108428             // give focus back to gridview
108429             //me.el.focus();
108430         }
108431     },
108432
108433     processItemEvent: function(record, row, rowIndex, e) {
108434         var me = this,
108435             cell = e.getTarget(me.cellSelector, row),
108436             cellIndex = cell ? cell.cellIndex : -1,
108437             map = me.statics().EventMap,
108438             selModel = me.getSelectionModel(),
108439             type = e.type,
108440             result;
108441
108442         if (type == 'keydown' && !cell && selModel.getCurrentPosition) {
108443             // CellModel, otherwise we can't tell which cell to invoke
108444             cell = me.getCellByPosition(selModel.getCurrentPosition());
108445             if (cell) {
108446                 cell = cell.dom;
108447                 cellIndex = cell.cellIndex;
108448             }
108449         }
108450
108451         result = me.fireEvent('uievent', type, me, cell, rowIndex, cellIndex, e);
108452
108453         if (result === false || me.callParent(arguments) === false) {
108454             return false;
108455         }
108456
108457         // Don't handle cellmouseenter and cellmouseleave events for now
108458         if (type == 'mouseover' || type == 'mouseout') {
108459             return true;
108460         }
108461
108462         return !(
108463             // We are adding cell and feature events  
108464             (me['onBeforeCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
108465             (me.fireEvent('beforecell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false) ||
108466             (me['onCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
108467             (me.fireEvent('cell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false)
108468         );
108469     },
108470
108471     processSpecialEvent: function(e) {
108472         var me = this,
108473             map = me.statics().EventMap,
108474             features = me.features,
108475             ln = features.length,
108476             type = e.type,
108477             i, feature, prefix, featureTarget,
108478             beforeArgs, args,
108479             panel = me.ownerCt;
108480
108481         me.callParent(arguments);
108482
108483         if (type == 'mouseover' || type == 'mouseout') {
108484             return;
108485         }
108486
108487         for (i = 0; i < ln; i++) {
108488             feature = features[i];
108489             if (feature.hasFeatureEvent) {
108490                 featureTarget = e.getTarget(feature.eventSelector, me.getTargetEl());
108491                 if (featureTarget) {
108492                     prefix = feature.eventPrefix;
108493                     // allows features to implement getFireEventArgs to change the
108494                     // fireEvent signature
108495                     beforeArgs = feature.getFireEventArgs('before' + prefix + type, me, featureTarget, e);
108496                     args = feature.getFireEventArgs(prefix + type, me, featureTarget, e);
108497                     
108498                     if (
108499                         // before view event
108500                         (me.fireEvent.apply(me, beforeArgs) === false) ||
108501                         // panel grid event
108502                         (panel.fireEvent.apply(panel, beforeArgs) === false) ||
108503                         // view event
108504                         (me.fireEvent.apply(me, args) === false) ||
108505                         // panel event
108506                         (panel.fireEvent.apply(panel, args) === false)
108507                     ) {
108508                         return false;
108509                     }
108510                 }
108511             }
108512         }
108513         return true;
108514     },
108515
108516     onCellMouseDown: Ext.emptyFn,
108517     onCellMouseUp: Ext.emptyFn,
108518     onCellClick: Ext.emptyFn,
108519     onCellDblClick: Ext.emptyFn,
108520     onCellContextMenu: Ext.emptyFn,
108521     onCellKeyDown: Ext.emptyFn,
108522     onBeforeCellMouseDown: Ext.emptyFn,
108523     onBeforeCellMouseUp: Ext.emptyFn,
108524     onBeforeCellClick: Ext.emptyFn,
108525     onBeforeCellDblClick: Ext.emptyFn,
108526     onBeforeCellContextMenu: Ext.emptyFn,
108527     onBeforeCellKeyDown: Ext.emptyFn,
108528
108529     /**
108530      * Expand a particular header to fit the max content width.
108531      * This will ONLY expand, not contract.
108532      * @private
108533      */
108534     expandToFit: function(header) {
108535         var maxWidth = this.getMaxContentWidth(header);
108536         delete header.flex;
108537         header.setWidth(maxWidth);
108538     },
108539
108540     /**
108541      * Get the max contentWidth of the header's text and all cells
108542      * in the grid under this header.
108543      * @private
108544      */
108545     getMaxContentWidth: function(header) {
108546         var cellSelector = header.getCellInnerSelector(),
108547             cells        = this.el.query(cellSelector),
108548             i = 0,
108549             ln = cells.length,
108550             maxWidth = header.el.dom.scrollWidth,
108551             scrollWidth;
108552
108553         for (; i < ln; i++) {
108554             scrollWidth = cells[i].scrollWidth;
108555             if (scrollWidth > maxWidth) {
108556                 maxWidth = scrollWidth;
108557             }
108558         }
108559         return maxWidth;
108560     },
108561
108562     getPositionByEvent: function(e) {
108563         var me       = this,
108564             cellNode = e.getTarget(me.cellSelector),
108565             rowNode  = e.getTarget(me.itemSelector),
108566             record   = me.getRecord(rowNode),
108567             header   = me.getHeaderByCell(cellNode);
108568
108569         return me.getPosition(record, header);
108570     },
108571
108572     getHeaderByCell: function(cell) {
108573         if (cell) {
108574             var m = cell.className.match(this.cellRe);
108575             if (m && m[1]) {
108576                 return Ext.getCmp(m[1]);
108577             }
108578         }
108579         return false;
108580     },
108581
108582     /**
108583      * @param {Object} position The current row and column: an object containing the following properties:<ul>
108584      * <li>row<div class="sub-desc"> The row <b>index</b></div></li>
108585      * <li>column<div class="sub-desc">The column <b>index</b></div></li>
108586      * </ul>
108587      * @param {String} direction 'up', 'down', 'right' and 'left'
108588      * @param {Ext.EventObject} e event
108589      * @param {Boolean} preventWrap Set to true to prevent wrap around to the next or previous row.
108590      * @param {Function} verifierFn A function to verify the validity of the calculated position. When using this function, you must return true to allow the newPosition to be returned.
108591      * @param {Scope} scope Scope to run the verifierFn in
108592      * @returns {Object} newPosition An object containing the following properties:<ul>
108593      * <li>row<div class="sub-desc"> The row <b>index</b></div></li>
108594      * <li>column<div class="sub-desc">The column <b>index</b></div></li>
108595      * </ul>
108596      * @private
108597      */
108598     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
108599         var me       = this,
108600             row      = pos.row,
108601             column   = pos.column,
108602             rowCount = me.store.getCount(),
108603             firstCol = me.getFirstVisibleColumnIndex(),
108604             lastCol  = me.getLastVisibleColumnIndex(),
108605             newPos   = {row: row, column: column},
108606             activeHeader = me.headerCt.getHeaderAtIndex(column);
108607
108608         // no active header or its currently hidden
108609         if (!activeHeader || activeHeader.hidden) {
108610             return false;
108611         }
108612
108613         e = e || {};
108614         direction = direction.toLowerCase();
108615         switch (direction) {
108616             case 'right':
108617                 // has the potential to wrap if its last
108618                 if (column === lastCol) {
108619                     // if bottom row and last column, deny right
108620                     if (preventWrap || row === rowCount - 1) {
108621                         return false;
108622                     }
108623                     if (!e.ctrlKey) {
108624                         // otherwise wrap to nextRow and firstCol
108625                         newPos.row = row + 1;
108626                         newPos.column = firstCol;
108627                     }
108628                 // go right
108629                 } else {
108630                     if (!e.ctrlKey) {
108631                         newPos.column = column + me.getRightGap(activeHeader);
108632                     } else {
108633                         newPos.column = lastCol;
108634                     }
108635                 }
108636                 break;
108637
108638             case 'left':
108639                 // has the potential to wrap
108640                 if (column === firstCol) {
108641                     // if top row and first column, deny left
108642                     if (preventWrap || row === 0) {
108643                         return false;
108644                     }
108645                     if (!e.ctrlKey) {
108646                         // otherwise wrap to prevRow and lastCol
108647                         newPos.row = row - 1;
108648                         newPos.column = lastCol;
108649                     }
108650                 // go left
108651                 } else {
108652                     if (!e.ctrlKey) {
108653                         newPos.column = column + me.getLeftGap(activeHeader);
108654                     } else {
108655                         newPos.column = firstCol;
108656                     }
108657                 }
108658                 break;
108659
108660             case 'up':
108661                 // if top row, deny up
108662                 if (row === 0) {
108663                     return false;
108664                 // go up
108665                 } else {
108666                     if (!e.ctrlKey) {
108667                         newPos.row = row - 1;
108668                     } else {
108669                         newPos.row = 0;
108670                     }
108671                 }
108672                 break;
108673
108674             case 'down':
108675                 // if bottom row, deny down
108676                 if (row === rowCount - 1) {
108677                     return false;
108678                 // go down
108679                 } else {
108680                     if (!e.ctrlKey) {
108681                         newPos.row = row + 1;
108682                     } else {
108683                         newPos.row = rowCount - 1;
108684                     }
108685                 }
108686                 break;
108687         }
108688
108689         if (verifierFn && verifierFn.call(scope || window, newPos) !== true) {
108690             return false;
108691         } else {
108692             return newPos;
108693         }
108694     },
108695     getFirstVisibleColumnIndex: function() {
108696         var headerCt   = this.getHeaderCt(),
108697             allColumns = headerCt.getGridColumns(),
108698             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
108699             firstHeader = visHeaders[0];
108700
108701         return headerCt.getHeaderIndex(firstHeader);
108702     },
108703
108704     getLastVisibleColumnIndex: function() {
108705         var headerCt   = this.getHeaderCt(),
108706             allColumns = headerCt.getGridColumns(),
108707             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
108708             lastHeader = visHeaders[visHeaders.length - 1];
108709
108710         return headerCt.getHeaderIndex(lastHeader);
108711     },
108712
108713     getHeaderCt: function() {
108714         return this.headerCt;
108715     },
108716
108717     getPosition: function(record, header) {
108718         var me = this,
108719             store = me.store,
108720             gridCols = me.headerCt.getGridColumns();
108721
108722         return {
108723             row: store.indexOf(record),
108724             column: Ext.Array.indexOf(gridCols, header)
108725         };
108726     },
108727
108728     /**
108729      * Determines the 'gap' between the closest adjacent header to the right
108730      * that is not hidden.
108731      * @private
108732      */
108733     getRightGap: function(activeHeader) {
108734         var headerCt        = this.getHeaderCt(),
108735             headers         = headerCt.getGridColumns(),
108736             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
108737             i               = activeHeaderIdx + 1,
108738             nextIdx;
108739
108740         for (; i <= headers.length; i++) {
108741             if (!headers[i].hidden) {
108742                 nextIdx = i;
108743                 break;
108744             }
108745         }
108746
108747         return nextIdx - activeHeaderIdx;
108748     },
108749
108750     beforeDestroy: function() {
108751         if (this.rendered) {
108752             this.el.removeAllListeners();
108753         }
108754         this.callParent(arguments);
108755     },
108756
108757     /**
108758      * Determines the 'gap' between the closest adjacent header to the left
108759      * that is not hidden.
108760      * @private
108761      */
108762     getLeftGap: function(activeHeader) {
108763         var headerCt        = this.getHeaderCt(),
108764             headers         = headerCt.getGridColumns(),
108765             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
108766             i               = activeHeaderIdx - 1,
108767             prevIdx;
108768
108769         for (; i >= 0; i--) {
108770             if (!headers[i].hidden) {
108771                 prevIdx = i;
108772                 break;
108773             }
108774         }
108775
108776         return prevIdx - activeHeaderIdx;
108777     }
108778 });
108779 /**
108780  * @class Ext.grid.View
108781  * @extends Ext.view.Table
108782
108783 The grid View class provides extra {@link Ext.grid.Panel} specific functionality to the
108784 {@link Ext.view.Table}. In general, this class is not instanced directly, instead a viewConfig
108785 option is passed to the grid:
108786
108787     Ext.create('Ext.grid.Panel', {
108788         // other options
108789         viewConfig: {
108790             stripeRows: false
108791         }
108792     });
108793     
108794 __Drag Drop__
108795 Drag and drop functionality can be achieved in the grid by attaching a {@link Ext.grid.plugin.DragDrop} plugin
108796 when creating the view.
108797
108798     Ext.create('Ext.grid.Panel', {
108799         // other options
108800         viewConfig: {
108801             plugins: {
108802                 ddGroup: 'people-group',
108803                 ptype: 'gridviewdragdrop',
108804                 enableDrop: false
108805             }
108806         }
108807     });
108808
108809  * @markdown
108810  */
108811 Ext.define('Ext.grid.View', {
108812     extend: 'Ext.view.Table',
108813     alias: 'widget.gridview',
108814
108815     /**
108816      * @cfg {Boolean} stripeRows <tt>true</tt> to stripe the rows. Default is <tt>false</tt>.
108817      * <p>This causes the CSS class <tt><b>x-grid-row-alt</b></tt> to be added to alternate rows of
108818      * the grid. A default CSS rule is provided which sets a background color, but you can override this
108819      * with a rule which either overrides the <b>background-color</b> style using the '!important'
108820      * modifier, or which uses a CSS selector of higher specificity.</p>
108821      */
108822     stripeRows: true,
108823     
108824     invalidateScrollerOnRefresh: true,
108825     
108826     /**
108827      * Scroll the GridView to the top by scrolling the scroller.
108828      * @private
108829      */
108830     scrollToTop : function(){
108831         if (this.rendered) {
108832             var section = this.ownerCt,
108833                 verticalScroller = section.verticalScroller;
108834                 
108835             if (verticalScroller) {
108836                 verticalScroller.scrollToTop();
108837             }
108838         }
108839     },
108840
108841     // after adding a row stripe rows from then on
108842     onAdd: function(ds, records, index) {
108843         this.callParent(arguments);
108844         this.doStripeRows(index);
108845     },
108846     
108847     // after removing a row stripe rows from then on
108848     onRemove: function(ds, records, index) {
108849         this.callParent(arguments);
108850         this.doStripeRows(index);
108851     },
108852     
108853     onUpdate: function(ds, record, operation) {
108854         var index = ds.indexOf(record);
108855         this.callParent(arguments);
108856         this.doStripeRows(index, index);
108857     },
108858     
108859     /**
108860      * Stripe rows from a particular row index
108861      * @param {Number} startRow
108862      * @param {Number} endRow Optional argument specifying the last row to process. By default process up to the last row.
108863      * @private
108864      */
108865     doStripeRows: function(startRow, endRow) {
108866         // ensure stripeRows configuration is turned on
108867         if (this.stripeRows) {
108868             var rows   = this.getNodes(startRow, endRow),
108869                 rowsLn = rows.length,
108870                 i      = 0,
108871                 row;
108872                 
108873             for (; i < rowsLn; i++) {
108874                 row = rows[i];
108875                 // Remove prior applied row classes.
108876                 row.className = row.className.replace(this.rowClsRe, ' ');
108877                 startRow++;
108878                 // Every odd row will get an additional cls
108879                 if (startRow % 2 === 0) {
108880                     row.className += (' ' + this.altRowCls);
108881                 }
108882             }
108883         }
108884     },
108885     
108886     refresh: function(firstPass) {
108887         this.callParent(arguments);
108888         this.doStripeRows(0);
108889         // TODO: Remove gridpanel dependency
108890         var g = this.up('gridpanel');
108891         if (g && this.invalidateScrollerOnRefresh) {
108892             g.invalidateScroller();
108893         }
108894     }
108895 });
108896
108897 /**
108898  * @author Aaron Conran
108899  * @class Ext.grid.Panel
108900  * @extends Ext.panel.Table
108901  *
108902  * Grids are an excellent way of showing large amounts of tabular data on the client side. Essentially a supercharged 
108903  * `<table>`, GridPanel makes it easy to fetch, sort and filter large amounts of data.
108904  * 
108905  * Grids are composed of 2 main pieces - a {@link Ext.data.Store Store} full of data and a set of columns to render.
108906  *
108907  * {@img Ext.grid.Panel/Ext.grid.Panel1.png Ext.grid.Panel component}
108908  *
108909  * ## Basic GridPanel
108910  *
108911  *     Ext.create('Ext.data.Store', {
108912  *         storeId:'simpsonsStore',
108913  *         fields:['name', 'email', 'phone'],
108914  *         data:{'items':[
108915  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
108916  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
108917  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},                        
108918  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}            
108919  *         ]},
108920  *         proxy: {
108921  *             type: 'memory',
108922  *             reader: {
108923  *                 type: 'json',
108924  *                 root: 'items'
108925  *             }
108926  *         }
108927  *     });
108928  *     
108929  *     Ext.create('Ext.grid.Panel', {
108930  *         title: 'Simpsons',
108931  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
108932  *         columns: [
108933  *             {header: 'Name',  dataIndex: 'name'},
108934  *             {header: 'Email', dataIndex: 'email', flex:1},
108935  *             {header: 'Phone', dataIndex: 'phone'}
108936  *         ],
108937  *         height: 200,
108938  *         width: 400,
108939  *         renderTo: Ext.getBody()
108940  *     });
108941  * 
108942  * The code above produces a simple grid with three columns. We specified a Store which will load JSON data inline. 
108943  * In most apps we would be placing the grid inside another container and wouldn't need to use the
108944  * {@link #height}, {@link #width} and {@link #renderTo} configurations but they are included here to make it easy to get
108945  * up and running.
108946  * 
108947  * The grid we created above will contain a header bar with a title ('Simpsons'), a row of column headers directly underneath
108948  * and finally the grid rows under the headers.
108949  * 
108950  * ## Configuring columns
108951  * 
108952  * By default, each column is sortable and will toggle between ASC and DESC sorting when you click on its header. Each
108953  * column header is also reorderable by default, and each gains a drop-down menu with options to hide and show columns.
108954  * It's easy to configure each column - here we use the same example as above and just modify the columns config:
108955  * 
108956  *     columns: [
108957  *         {
108958  *             header: 'Name',
108959  *             dataIndex: 'name',
108960  *             sortable: false,
108961  *             hideable: false,
108962  *             flex: 1
108963  *         },
108964  *         {
108965  *             header: 'Email',
108966  *             dataIndex: 'email',
108967  *             hidden: true
108968  *         },
108969  *         {
108970  *             header: 'Phone',
108971  *             dataIndex: 'phone',
108972  *             width: 100
108973  *         }
108974  *     ]
108975  * 
108976  * We turned off sorting and hiding on the 'Name' column so clicking its header now has no effect. We also made the Email
108977  * column hidden by default (it can be shown again by using the menu on any other column). We also set the Phone column to
108978  * a fixed with of 100px and flexed the Name column, which means it takes up all remaining width after the other columns 
108979  * have been accounted for. See the {@link Ext.grid.column.Column column docs} for more details.
108980  * 
108981  * ## Renderers
108982  * 
108983  * As well as customizing columns, it's easy to alter the rendering of individual cells using renderers. A renderer is 
108984  * tied to a particular column and is passed the value that would be rendered into each cell in that column. For example,
108985  * we could define a renderer function for the email column to turn each email address into a mailto link:
108986  * 
108987  *     columns: [
108988  *         {
108989  *             header: 'Email',
108990  *             dataIndex: 'email',
108991  *             renderer: function(value) {
108992  *                 return Ext.String.format('<a href="mailto:{0}">{1}</a>', value, value);
108993  *             }
108994  *         }
108995  *     ]
108996  * 
108997  * See the {@link Ext.grid.column.Column column docs} for more information on renderers.
108998  * 
108999  * ## Selection Models
109000  * 
109001  * Sometimes all you want is to render data onto the screen for viewing, but usually it's necessary to interact with or 
109002  * update that data. Grids use a concept called a Selection Model, which is simply a mechanism for selecting some part of
109003  * the data in the grid. The two main types of Selection Model are RowSelectionModel, where entire rows are selected, and
109004  * CellSelectionModel, where individual cells are selected.
109005  * 
109006  * Grids use a Row Selection Model by default, but this is easy to customise like so:
109007  * 
109008  *     Ext.create('Ext.grid.Panel', {
109009  *         selType: 'cellmodel',
109010  *         store: ...
109011  *     });
109012  * 
109013  * Specifying the `cellmodel` changes a couple of things. Firstly, clicking on a cell now
109014  * selects just that cell (using a {@link Ext.selection.RowModel rowmodel} will select the entire row), and secondly the
109015  * keyboard navigation will walk from cell to cell instead of row to row. Cell-based selection models are usually used in
109016  * conjunction with editing.
109017  * 
109018  * {@img Ext.grid.Panel/Ext.grid.Panel2.png Ext.grid.Panel cell editing}
109019  *
109020  * ## Editing
109021  * 
109022  * Grid has built-in support for in-line editing. There are two chief editing modes - cell editing and row editing. Cell
109023  * editing is easy to add to your existing column setup - here we'll just modify the example above to include an editor
109024  * on both the name and the email columns:
109025  * 
109026  *     Ext.create('Ext.grid.Panel', {
109027  *         title: 'Simpsons',
109028  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
109029  *         columns: [
109030  *             {header: 'Name',  dataIndex: 'name', field: 'textfield'},
109031  *             {header: 'Email', dataIndex: 'email', flex:1, 
109032  *                 field:{
109033  *                     xtype:'textfield',
109034  *                     allowBlank:false
109035  *                 }
109036  *             },
109037  *             {header: 'Phone', dataIndex: 'phone'}
109038  *         ],
109039  *         selType: 'cellmodel',
109040  *         plugins: [
109041  *             Ext.create('Ext.grid.plugin.CellEditing', {
109042  *                 clicksToEdit: 1
109043  *             })
109044  *         ],
109045  *         height: 200,
109046  *         width: 400,
109047  *         renderTo: Ext.getBody()
109048  *     });
109049  * 
109050  * This requires a little explanation. We're passing in {@link #store store} and {@link #columns columns} as normal, but 
109051  * this time we've also specified a {@link #field field} on two of our columns. For the Name column we just want a default
109052  * textfield to edit the value, so we specify 'textfield'. For the Email column we customized the editor slightly by 
109053  * passing allowBlank: false, which will provide inline validation.
109054  * 
109055  * To support cell editing, we also specified that the grid should use the 'cellmodel' {@link #selType}, and created an
109056  * instance of the {@link Ext.grid.plugin.CellEditing CellEditing plugin}, which we configured to activate each editor after a
109057  * single click.
109058  * 
109059  * {@img Ext.grid.Panel/Ext.grid.Panel3.png Ext.grid.Panel row editing}
109060  *
109061  * ## Row Editing
109062  * 
109063  * The other type of editing is row-based editing, using the RowEditor component. This enables you to edit an entire row
109064  * at a time, rather than editing cell by cell. Row Editing works in exactly the same way as cell editing, all we need to
109065  * do is change the plugin type to {@link Ext.grid.plugin.RowEditing}, and set the selType to 'rowmodel':
109066  * 
109067  *     Ext.create('Ext.grid.Panel', {
109068  *         title: 'Simpsons',
109069  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
109070  *         columns: [
109071  *             {header: 'Name',  dataIndex: 'name', field: 'textfield'},
109072  *             {header: 'Email', dataIndex: 'email', flex:1, 
109073  *                 field:{
109074  *                     xtype:'textfield',
109075  *                     allowBlank:false
109076  *                 }
109077  *             },
109078  *             {header: 'Phone', dataIndex: 'phone'}
109079  *         ],
109080  *         selType: 'rowmodel',
109081  *         plugins: [
109082  *             Ext.create('Ext.grid.plugin.RowEditing', {
109083  *                 clicksToEdit: 1
109084  *             })
109085  *         ],
109086  *         height: 200,
109087  *         width: 400,
109088  *         renderTo: Ext.getBody()
109089  *     });
109090  * 
109091  * Again we passed some configuration to our {@link Ext.grid.plugin.RowEditing} plugin, and now when we click each row a row
109092  * editor will appear and enable us to edit each of the columns we have specified an editor for.
109093  * 
109094  * ## Sorting & Filtering
109095  * 
109096  * Every grid is attached to a {@link Ext.data.Store Store}, which provides multi-sort and filtering capabilities. It's
109097  * easy to set up a grid to be sorted from the start:
109098  * 
109099  *     var myGrid = Ext.create('Ext.grid.Panel', {
109100  *         store: {
109101  *             fields: ['name', 'email', 'phone'],
109102  *             sorters: ['name', 'phone']
109103  *         },
109104  *         columns: [
109105  *             {text: 'Name',  dataIndex: 'name'},
109106  *             {text: 'Email', dataIndex: 'email'}
109107  *         ]
109108  *     });
109109  * 
109110  * Sorting at run time is easily accomplished by simply clicking each column header. If you need to perform sorting on 
109111  * more than one field at run time it's easy to do so by adding new sorters to the store:
109112  * 
109113  *     myGrid.store.sort([
109114  *         {property: 'name',  direction: 'ASC'},
109115  *         {property: 'email', direction: 'DESC'},
109116  *     ]);
109117  * 
109118  * {@img Ext.grid.Panel/Ext.grid.Panel4.png Ext.grid.Panel grouping}
109119  * 
109120  * ## Grouping
109121  * 
109122  * Grid supports the grouping of rows by any field. For example if we had a set of employee records, we might want to 
109123  * group by the department that each employee works in. Here's how we might set that up:
109124  * 
109125  *     var store = Ext.create('Ext.data.Store', {
109126  *         storeId:'employeeStore',
109127  *         fields:['name', 'senority', 'department'],
109128  *         groupField: 'department',
109129  *         data:{'employees':[
109130  *             {"name":"Michael Scott", "senority":7, "department":"Manangement"},
109131  *             {"name":"Dwight Schrute", "senority":2, "department":"Sales"},
109132  *             {"name":"Jim Halpert", "senority":3, "department":"Sales"},
109133  *             {"name":"Kevin Malone", "senority":4, "department":"Accounting"},
109134  *             {"name":"Angela Martin", "senority":5, "department":"Accounting"}                        
109135  *         ]},
109136  *         proxy: {
109137  *             type: 'memory',
109138  *             reader: {
109139  *                 type: 'json',
109140  *                 root: 'employees'
109141  *             }
109142  *         }
109143  *     });
109144  *     
109145  *     Ext.create('Ext.grid.Panel', {
109146  *         title: 'Employees',
109147  *         store: Ext.data.StoreManager.lookup('employeeStore'),
109148  *         columns: [
109149  *             {header: 'Name',  dataIndex: 'name'},
109150  *             {header: 'Senority', dataIndex: 'senority'}
109151  *         ],        
109152  *         features: [{ftype:'grouping'}],
109153  *         width: 200,
109154  *         height: 275,
109155  *         renderTo: Ext.getBody()
109156  *     });
109157  * 
109158  * ## Infinite Scrolling
109159  *
109160  * Grid supports infinite scrolling as an alternative to using a paging toolbar. Your users can scroll through thousands
109161  * of records without the performance penalties of renderering all the records on screen at once. The grid should be bound
109162  * to a store with a pageSize specified.
109163  *
109164  *     var grid = Ext.create('Ext.grid.Panel', {
109165  *         // Use a PagingGridScroller (this is interchangeable with a PagingToolbar)
109166  *         verticalScrollerType: 'paginggridscroller',
109167  *         // do not reset the scrollbar when the view refreshs
109168  *         invalidateScrollerOnRefresh: false,
109169  *         // infinite scrolling does not support selection
109170  *         disableSelection: true,
109171  *         // ...
109172  *     });
109173  * 
109174  * ## Paging
109175  *
109176  * Grid supports paging through large sets of data via a PagingToolbar or PagingGridScroller (see the Infinite Scrolling section above).
109177  * To leverage paging via a toolbar or scroller, you need to set a pageSize configuration on the Store.
109178  *
109179  *     var itemsPerPage = 2;   // set the number of items you want per page
109180  *     
109181  *     var store = Ext.create('Ext.data.Store', {
109182  *         id:'simpsonsStore',
109183  *         autoLoad: false,
109184  *         fields:['name', 'email', 'phone'],
109185  *         pageSize: itemsPerPage, // items per page
109186  *         proxy: {
109187  *             type: 'ajax',
109188  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
109189  *             reader: {
109190  *                 type: 'json',
109191  *                 root: 'items',
109192  *                 totalProperty: 'total'
109193  *             }
109194  *         }
109195  *     });
109196  *     
109197  *     // specify segment of data you want to load using params
109198  *     store.load({
109199  *         params:{
109200  *             start:0,    
109201  *             limit: itemsPerPage
109202  *         }
109203  *     });
109204  *     
109205  *     Ext.create('Ext.grid.Panel', {
109206  *         title: 'Simpsons',
109207  *         store: store,
109208  *         columns: [
109209  *             {header: 'Name',  dataIndex: 'name'},
109210  *             {header: 'Email', dataIndex: 'email', flex:1},
109211  *             {header: 'Phone', dataIndex: 'phone'}
109212  *         ],
109213  *         width: 400,
109214  *         height: 125,
109215  *         dockedItems: [{
109216  *             xtype: 'pagingtoolbar',
109217  *             store: store,   // same store GridPanel is using
109218  *             dock: 'bottom',
109219  *             displayInfo: true
109220  *         }],
109221  *         renderTo: Ext.getBody()
109222  *     }); 
109223  * 
109224  * {@img Ext.grid.Panel/Ext.grid.Panel5.png Ext.grid.Panel grouping}
109225  * 
109226  * @docauthor Ed Spencer
109227  */
109228 Ext.define('Ext.grid.Panel', {
109229     extend: 'Ext.panel.Table',
109230     requires: ['Ext.grid.View'],
109231     alias: ['widget.gridpanel', 'widget.grid'],
109232     alternateClassName: ['Ext.list.ListView', 'Ext.ListView', 'Ext.grid.GridPanel'],
109233     viewType: 'gridview',
109234     
109235     lockable: false,
109236     
109237     // Required for the Lockable Mixin. These are the configurations which will be copied to the
109238     // normal and locked sub tablepanels
109239     normalCfgCopy: ['invalidateScrollerOnRefresh', 'verticalScroller', 'verticalScrollDock', 'verticalScrollerType', 'scroll'],
109240     lockedCfgCopy: ['invalidateScrollerOnRefresh'],
109241     
109242     /**
109243      * @cfg {Boolean} columnLines Adds column line styling
109244      */
109245     
109246     initComponent: function() {
109247         var me = this;
109248
109249         if (me.columnLines) {
109250             me.setColumnLines(me.columnLines);
109251         }
109252         
109253         me.callParent();
109254     },
109255     
109256     setColumnLines: function(show) {
109257         var me = this,
109258             method = (show) ? 'addClsWithUI' : 'removeClsWithUI';
109259         
109260         me[method]('with-col-lines')
109261     }
109262 });
109263 // Currently has the following issues:
109264 // - Does not handle postEditValue
109265 // - Fields without editors need to sync with their values in Store
109266 // - starting to edit another record while already editing and dirty should probably prevent it
109267 // - aggregating validation messages
109268 // - tabIndex is not managed bc we leave elements in dom, and simply move via positioning
109269 // - layout issues when changing sizes/width while hidden (layout bug)
109270
109271 /**
109272  * @class Ext.grid.RowEditor
109273  * @extends Ext.form.Panel
109274  *
109275  * Internal utility class used to provide row editing functionality. For developers, they should use
109276  * the RowEditing plugin to use this functionality with a grid.
109277  *
109278  * @ignore
109279  */
109280 Ext.define('Ext.grid.RowEditor', {
109281     extend: 'Ext.form.Panel',
109282     requires: [
109283         'Ext.tip.ToolTip',
109284         'Ext.util.HashMap',
109285         'Ext.util.KeyNav'
109286     ],
109287
109288     saveBtnText  : 'Update',
109289     cancelBtnText: 'Cancel',
109290     errorsText: 'Errors',
109291     dirtyText: 'You need to commit or cancel your changes',
109292
109293     lastScrollLeft: 0,
109294     lastScrollTop: 0,
109295
109296     border: false,
109297     
109298     // Change the hideMode to offsets so that we get accurate measurements when
109299     // the roweditor is hidden for laying out things like a TriggerField.
109300     hideMode: 'offsets',
109301
109302     initComponent: function() {
109303         var me = this,
109304             form;
109305
109306         me.cls = Ext.baseCSSPrefix + 'grid-row-editor';
109307
109308         me.layout = {
109309             type: 'hbox',
109310             align: 'middle'
109311         };
109312
109313         // Maintain field-to-column mapping
109314         // It's easy to get a field from a column, but not vice versa
109315         me.columns = Ext.create('Ext.util.HashMap');
109316         me.columns.getKey = function(columnHeader) {
109317             var f;
109318             if (columnHeader.getEditor) {
109319                 f = columnHeader.getEditor();
109320                 if (f) {
109321                     return f.id;
109322                 }
109323             }
109324             return columnHeader.id;
109325         };
109326         me.mon(me.columns, {
109327             add: me.onFieldAdd,
109328             remove: me.onFieldRemove,
109329             replace: me.onFieldReplace,
109330             scope: me
109331         });
109332
109333         me.callParent(arguments);
109334
109335         if (me.fields) {
109336             me.setField(me.fields);
109337             delete me.fields;
109338         }
109339
109340         form = me.getForm();
109341         form.trackResetOnLoad = true;
109342     },
109343
109344     onFieldChange: function() {
109345         var me = this,
109346             form = me.getForm(),
109347             valid = form.isValid();
109348         if (me.errorSummary && me.isVisible()) {
109349             me[valid ? 'hideToolTip' : 'showToolTip']();
109350         }
109351         if (me.floatingButtons) {
109352             me.floatingButtons.child('#update').setDisabled(!valid);
109353         }
109354         me.isValid = valid;
109355     },
109356
109357     afterRender: function() {
109358         var me = this,
109359             plugin = me.editingPlugin;
109360
109361         me.callParent(arguments);
109362         me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });
109363
109364         // Prevent from bubbling click events to the grid view
109365         me.mon(me.el, {
109366             click: Ext.emptyFn,
109367             stopPropagation: true
109368         });
109369
109370         me.el.swallowEvent([
109371             'keypress',
109372             'keydown'
109373         ]);
109374
109375         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
109376             enter: plugin.completeEdit,
109377             esc: plugin.onEscKey,
109378             scope: plugin
109379         });
109380
109381         me.mon(plugin.view, {
109382             beforerefresh: me.onBeforeViewRefresh,
109383             refresh: me.onViewRefresh,
109384             scope: me
109385         });
109386     },
109387
109388     onBeforeViewRefresh: function(view) {
109389         var me = this,
109390             viewDom = view.el.dom;
109391
109392         if (me.el.dom.parentNode === viewDom) {
109393             viewDom.removeChild(me.el.dom);
109394         }
109395     },
109396
109397     onViewRefresh: function(view) {
109398         var me = this,
109399             viewDom = view.el.dom,
109400             context = me.context,
109401             idx;
109402
109403         viewDom.appendChild(me.el.dom);
109404
109405         // Recover our row node after a view refresh
109406         if (context && (idx = context.store.indexOf(context.record)) >= 0) {
109407             context.row = view.getNode(idx);
109408             me.reposition();
109409             if (me.tooltip && me.tooltip.isVisible()) {
109410                 me.tooltip.setTarget(context.row);
109411             }
109412         } else {
109413             me.editingPlugin.cancelEdit();
109414         }
109415     },
109416
109417     onCtScroll: function(e, target) {
109418         var me = this,
109419             scrollTop  = target.scrollTop,
109420             scrollLeft = target.scrollLeft;
109421
109422         if (scrollTop !== me.lastScrollTop) {
109423             me.lastScrollTop = scrollTop;
109424             if ((me.tooltip && me.tooltip.isVisible()) || me.hiddenTip) {
109425                 me.repositionTip();
109426             }
109427         }
109428         if (scrollLeft !== me.lastScrollLeft) {
109429             me.lastScrollLeft = scrollLeft;
109430             me.reposition();
109431         }
109432     },
109433
109434     onColumnAdd: function(column) {
109435         this.setField(column);
109436     },
109437
109438     onColumnRemove: function(column) {
109439         this.columns.remove(column);
109440     },
109441
109442     onColumnResize: function(column, width) {
109443         column.getEditor().setWidth(width - 2);
109444         if (this.isVisible()) {
109445             this.reposition();
109446         }
109447     },
109448
109449     onColumnHide: function(column) {
109450         column.getEditor().hide();
109451         if (this.isVisible()) {
109452             this.reposition();
109453         }
109454     },
109455
109456     onColumnShow: function(column) {
109457         var field = column.getEditor();
109458         field.setWidth(column.getWidth() - 2).show();
109459         if (this.isVisible()) {
109460             this.reposition();
109461         }
109462     },
109463
109464     onColumnMove: function(column, fromIdx, toIdx) {
109465         var field = column.getEditor();
109466         if (this.items.indexOf(field) != toIdx) {
109467             this.move(fromIdx, toIdx);
109468         }
109469     },
109470
109471     onFieldAdd: function(map, fieldId, column) {
109472         var me = this,
109473             colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column),
109474             field = column.getEditor({ xtype: 'displayfield' });
109475
109476         me.insert(colIdx, field);
109477     },
109478
109479     onFieldRemove: function(map, fieldId, column) {
109480         var me = this,
109481             field = column.getEditor(),
109482             fieldEl = field.el;
109483         me.remove(field, false);
109484         if (fieldEl) {
109485             fieldEl.remove();
109486         }
109487     },
109488
109489     onFieldReplace: function(map, fieldId, column, oldColumn) {
109490         var me = this;
109491         me.onFieldRemove(map, fieldId, oldColumn);
109492     },
109493
109494     clearFields: function() {
109495         var me = this,
109496             map = me.columns;
109497         map.each(function(fieldId) {
109498             map.removeAtKey(fieldId);
109499         });
109500     },
109501
109502     getFloatingButtons: function() {
109503         var me = this,
109504             cssPrefix = Ext.baseCSSPrefix,
109505             btnsCss = cssPrefix + 'grid-row-editor-buttons',
109506             plugin = me.editingPlugin,
109507             btns;
109508
109509         if (!me.floatingButtons) {
109510             btns = me.floatingButtons = Ext.create('Ext.Container', {
109511                 renderTpl: [
109512                     '<div class="{baseCls}-ml"></div>',
109513                     '<div class="{baseCls}-mr"></div>',
109514                     '<div class="{baseCls}-bl"></div>',
109515                     '<div class="{baseCls}-br"></div>',
109516                     '<div class="{baseCls}-bc"></div>'
109517                 ],
109518
109519                 renderTo: me.el,
109520                 baseCls: btnsCss,
109521                 layout: {
109522                     type: 'hbox',
109523                     align: 'middle'
109524                 },
109525                 defaults: {
109526                     margins: '0 1 0 1'
109527                 },
109528                 items: [{
109529                     itemId: 'update',
109530                     flex: 1,
109531                     xtype: 'button',
109532                     handler: plugin.completeEdit,
109533                     scope: plugin,
109534                     text: me.saveBtnText,
109535                     disabled: !me.isValid
109536                 }, {
109537                     flex: 1,
109538                     xtype: 'button',
109539                     handler: plugin.cancelEdit,
109540                     scope: plugin,
109541                     text: me.cancelBtnText
109542                 }]
109543             });
109544
109545             // Prevent from bubbling click events to the grid view
109546             me.mon(btns.el, {
109547                 // BrowserBug: Opera 11.01
109548                 //   causes the view to scroll when a button is focused from mousedown
109549                 mousedown: Ext.emptyFn,
109550                 click: Ext.emptyFn,
109551                 stopEvent: true
109552             });
109553         }
109554         return me.floatingButtons;
109555     },
109556
109557     reposition: function(animateConfig) {
109558         var me = this,
109559             context = me.context,
109560             row = context && Ext.get(context.row),
109561             btns = me.getFloatingButtons(),
109562             btnEl = btns.el,
109563             grid = me.editingPlugin.grid,
109564             viewEl = grid.view.el,
109565             scroller = grid.verticalScroller,
109566
109567             // always get data from ColumnModel as its what drives
109568             // the GridView's sizing
109569             mainBodyWidth = grid.headerCt.getFullWidth(),
109570             scrollerWidth = grid.getWidth(),
109571
109572             // use the minimum as the columns may not fill up the entire grid
109573             // width
109574             width = Math.min(mainBodyWidth, scrollerWidth),
109575             scrollLeft = grid.view.el.dom.scrollLeft,
109576             btnWidth = btns.getWidth(),
109577             left = (width - btnWidth) / 2 + scrollLeft,
109578             y, rowH, newHeight,
109579
109580             invalidateScroller = function() {
109581                 if (scroller) {
109582                     scroller.invalidate();
109583                     btnEl.scrollIntoView(viewEl, false);
109584                 }
109585                 if (animateConfig && animateConfig.callback) {
109586                     animateConfig.callback.call(animateConfig.scope || me);
109587                 }
109588             };
109589
109590         // need to set both top/left
109591         if (row && Ext.isElement(row.dom)) {
109592             // Bring our row into view if necessary, so a row editor that's already
109593             // visible and animated to the row will appear smooth
109594             row.scrollIntoView(viewEl, false);
109595
109596             // Get the y position of the row relative to its top-most static parent.
109597             // offsetTop will be relative to the table, and is incorrect
109598             // when mixed with certain grid features (e.g., grouping).
109599             y = row.getXY()[1] - 5;
109600             rowH = row.getHeight();
109601             newHeight = rowH + 10;
109602
109603             // IE doesn't set the height quite right.
109604             // This isn't a border-box issue, it even happens
109605             // in IE8 and IE7 quirks.
109606             // TODO: Test in IE9!
109607             if (Ext.isIE) {
109608                 newHeight += 2;
109609             }
109610
109611             // Set editor height to match the row height
109612             if (me.getHeight() != newHeight) {
109613                 me.setHeight(newHeight);
109614                 me.el.setLeft(0);
109615             }
109616
109617             if (animateConfig) {
109618                 var animObj = {
109619                     to: {
109620                         y: y
109621                     },
109622                     duration: animateConfig.duration || 125,
109623                     listeners: {
109624                         afteranimate: function() {
109625                             invalidateScroller();
109626                             y = row.getXY()[1] - 5;
109627                             me.el.setY(y);
109628                         }
109629                     }
109630                 };
109631                 me.animate(animObj);
109632             } else {
109633                 me.el.setY(y);
109634                 invalidateScroller();
109635             }
109636         }
109637         if (me.getWidth() != mainBodyWidth) {
109638             me.setWidth(mainBodyWidth);
109639         }
109640         btnEl.setLeft(left);
109641     },
109642
109643     getEditor: function(fieldInfo) {
109644         var me = this;
109645
109646         if (Ext.isNumber(fieldInfo)) {
109647             // Query only form fields. This just future-proofs us in case we add
109648             // other components to RowEditor later on.  Don't want to mess with
109649             // indices.
109650             return me.query('>[isFormField]')[fieldInfo];
109651         } else if (fieldInfo instanceof Ext.grid.column.Column) {
109652             return fieldInfo.getEditor();
109653         }
109654     },
109655
109656     removeField: function(field) {
109657         var me = this;
109658
109659         // Incase we pass a column instead, which is fine
109660         field = me.getEditor(field);
109661         me.mun(field, 'validitychange', me.onValidityChange, me);
109662
109663         // Remove field/column from our mapping, which will fire the event to
109664         // remove the field from our container
109665         me.columns.removeKey(field.id);
109666     },
109667
109668     setField: function(column) {
109669         var me = this,
109670             field;
109671
109672         if (Ext.isArray(column)) {
109673             Ext.Array.forEach(column, me.setField, me);
109674             return;
109675         }
109676
109677         // Get a default display field if necessary
109678         field = column.getEditor(null, {
109679             xtype: 'displayfield',
109680             // Default display fields will not return values. This is done because
109681             // the display field will pick up column renderers from the grid.
109682             getModelData: function() {
109683                 return null;
109684             }
109685         });
109686         field.margins = '0 0 0 2';
109687         field.setWidth(column.getDesiredWidth() - 2);
109688         me.mon(field, 'change', me.onFieldChange, me);
109689
109690         // Maintain mapping of fields-to-columns
109691         // This will fire events that maintain our container items
109692         me.columns.add(field.id, column);
109693         
109694         if (me.isVisible() && me.context) {
109695             me.renderColumnData(field, me.context.record);
109696         }
109697     },
109698
109699     loadRecord: function(record) {
109700         var me = this,
109701             form = me.getForm();
109702         form.loadRecord(record);
109703         if (form.isValid()) {
109704             me.hideToolTip();
109705         } else {
109706             me.showToolTip();
109707         }
109708
109709         // render display fields so they honor the column renderer/template
109710         Ext.Array.forEach(me.query('>displayfield'), function(field) {
109711             me.renderColumnData(field, record);
109712         }, me);
109713     },
109714
109715     renderColumnData: function(field, record) {
109716         var me = this,
109717             grid = me.editingPlugin.grid,
109718             headerCt = grid.headerCt,
109719             view = grid.view,
109720             store = view.store,
109721             column = me.columns.get(field.id),
109722             value = record.get(column.dataIndex);
109723
109724         // honor our column's renderer (TemplateHeader sets renderer for us!)
109725         if (column.renderer) {
109726             var metaData = { tdCls: '', style: '' },
109727                 rowIdx = store.indexOf(record),
109728                 colIdx = headerCt.getHeaderIndex(column);
109729
109730             value = column.renderer.call(
109731                 column.scope || headerCt.ownerCt,
109732                 value,
109733                 metaData,
109734                 record,
109735                 rowIdx,
109736                 colIdx,
109737                 store,
109738                 view
109739             );
109740         }
109741
109742         field.setRawValue(value);
109743         field.resetOriginalValue();
109744     },
109745
109746     beforeEdit: function() {
109747         var me = this;
109748
109749         if (me.isVisible() && !me.autoCancel && me.isDirty()) {
109750             me.showToolTip();
109751             return false;
109752         }
109753     },
109754
109755     /**
109756      * Start editing the specified grid at the specified position.
109757      * @param {Model} record The Store data record which backs the row to be edited.
109758      * @param {Model} columnHeader The Column object defining the column to be edited.
109759      */
109760     startEdit: function(record, columnHeader) {
109761         var me = this,
109762             grid = me.editingPlugin.grid,
109763             view = grid.getView(),
109764             store = grid.store,
109765             context = me.context = Ext.apply(me.editingPlugin.context, {
109766                 view: grid.getView(),
109767                 store: store
109768             });
109769
109770         // make sure our row is selected before editing
109771         context.grid.getSelectionModel().select(record);
109772
109773         // Reload the record data
109774         me.loadRecord(record);
109775
109776         if (!me.isVisible()) {
109777             me.show();
109778             me.focusContextCell();
109779         } else {
109780             me.reposition({
109781                 callback: this.focusContextCell
109782             });
109783         }
109784     },
109785
109786     // Focus the cell on start edit based upon the current context
109787     focusContextCell: function() {
109788         var field = this.getEditor(this.context.colIdx);
109789         if (field && field.focus) {
109790             field.focus();
109791         }
109792     },
109793
109794     cancelEdit: function() {
109795         var me = this,
109796             form = me.getForm();
109797
109798         me.hide();
109799         form.clearInvalid();
109800         form.reset();
109801     },
109802
109803     completeEdit: function() {
109804         var me = this,
109805             form = me.getForm();
109806
109807         if (!form.isValid()) {
109808             return;
109809         }
109810
109811         form.updateRecord(me.context.record);
109812         me.hide();
109813         return true;
109814     },
109815
109816     onShow: function() {
109817         var me = this;
109818         me.callParent(arguments);
109819         me.reposition();
109820     },
109821
109822     onHide: function() {
109823         var me = this;
109824         me.callParent(arguments);
109825         me.hideToolTip();
109826         me.invalidateScroller();
109827         if (me.context) {
109828             me.context.view.focus();
109829             me.context = null;
109830         }
109831     },
109832
109833     isDirty: function() {
109834         var me = this,
109835             form = me.getForm();
109836         return form.isDirty();
109837     },
109838
109839     getToolTip: function() {
109840         var me = this,
109841             tip;
109842
109843         if (!me.tooltip) {
109844             tip = me.tooltip = Ext.createWidget('tooltip', {
109845                 cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
109846                 title: me.errorsText,
109847                 autoHide: false,
109848                 closable: true,
109849                 closeAction: 'disable',
109850                 anchor: 'left'
109851             });
109852         }
109853         return me.tooltip;
109854     },
109855
109856     hideToolTip: function() {
109857         var me = this,
109858             tip = me.getToolTip();
109859         if (tip.rendered) {
109860             tip.disable();
109861         }
109862         me.hiddenTip = false;
109863     },
109864
109865     showToolTip: function() {
109866         var me = this,
109867             tip = me.getToolTip(),
109868             context = me.context,
109869             row = Ext.get(context.row),
109870             viewEl = context.grid.view.el;
109871
109872         tip.setTarget(row);
109873         tip.showAt([-10000, -10000]);
109874         tip.body.update(me.getErrors());
109875         tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
109876         me.repositionTip();
109877         tip.doLayout();
109878         tip.enable();
109879     },
109880
109881     repositionTip: function() {
109882         var me = this,
109883             tip = me.getToolTip(),
109884             context = me.context,
109885             row = Ext.get(context.row),
109886             viewEl = context.grid.view.el,
109887             viewHeight = viewEl.getHeight(),
109888             viewTop = me.lastScrollTop,
109889             viewBottom = viewTop + viewHeight,
109890             rowHeight = row.getHeight(),
109891             rowTop = row.dom.offsetTop,
109892             rowBottom = rowTop + rowHeight;
109893
109894         if (rowBottom > viewTop && rowTop < viewBottom) {
109895             tip.show();
109896             me.hiddenTip = false;
109897         } else {
109898             tip.hide();
109899             me.hiddenTip = true;
109900         }
109901     },
109902
109903     getErrors: function() {
109904         var me = this,
109905             dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',
109906             errors = [];
109907
109908         Ext.Array.forEach(me.query('>[isFormField]'), function(field) {
109909             errors = errors.concat(
109910                 Ext.Array.map(field.getErrors(), function(e) {
109911                     return '<li>' + e + '</li>';
109912                 })
109913             );
109914         }, me);
109915
109916         return dirtyText + '<ul>' + errors.join('') + '</ul>';
109917     },
109918
109919     invalidateScroller: function() {
109920         var me = this,
109921             context = me.context,
109922             scroller = context.grid.verticalScroller;
109923
109924         if (scroller) {
109925             scroller.invalidate();
109926         }
109927     }
109928 });
109929 /**
109930  * @class Ext.grid.header.Container
109931  * @extends Ext.container.Container
109932  * @private
109933  *
109934  * Container which holds headers and is docked at the top or bottom of a TablePanel.
109935  * The HeaderContainer drives resizing/moving/hiding of columns within the TableView.
109936  * As headers are hidden, moved or resized the headercontainer is responsible for
109937  * triggering changes within the view.
109938  *
109939  * @xtype headercontainer
109940  */
109941 Ext.define('Ext.grid.header.Container', {
109942     extend: 'Ext.container.Container',
109943     uses: [
109944         'Ext.grid.ColumnLayout',
109945         'Ext.grid.column.Column',
109946         'Ext.menu.Menu',
109947         'Ext.menu.CheckItem',
109948         'Ext.menu.Separator',
109949         'Ext.grid.plugin.HeaderResizer',
109950         'Ext.grid.plugin.HeaderReorderer'
109951     ],
109952     border: true,
109953
109954     alias: 'widget.headercontainer',
109955
109956     baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
109957     dock: 'top',
109958
109959     /**
109960      * @cfg {Number} weight
109961      * HeaderContainer overrides the default weight of 0 for all docked items to 100.
109962      * This is so that it has more priority over things like toolbars.
109963      */
109964     weight: 100,
109965     defaultType: 'gridcolumn',
109966     /**
109967      * @cfg {Number} defaultWidth
109968      * Width of the header if no width or flex is specified. Defaults to 100.
109969      */
109970     defaultWidth: 100,
109971
109972
109973     sortAscText: 'Sort Ascending',
109974     sortDescText: 'Sort Descending',
109975     sortClearText: 'Clear Sort',
109976     columnsText: 'Columns',
109977
109978     lastHeaderCls: Ext.baseCSSPrefix + 'column-header-last',
109979     firstHeaderCls: Ext.baseCSSPrefix + 'column-header-first',
109980     headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
109981
109982     // private; will probably be removed by 4.0
109983     triStateSort: false,
109984
109985     ddLock: false,
109986
109987     dragging: false,
109988
109989     /**
109990      * <code>true</code> if this HeaderContainer is in fact a group header which contains sub headers.
109991      * @type Boolean
109992      * @property isGroupHeader
109993      */
109994
109995     /**
109996      * @cfg {Boolean} sortable
109997      * Provides the default sortable state for all Headers within this HeaderContainer.
109998      * Also turns on or off the menus in the HeaderContainer. Note that the menu is
109999      * shared across every header and therefore turning it off will remove the menu
110000      * items for every header.
110001      */
110002     sortable: true,
110003     
110004     initComponent: function() {
110005         var me = this;
110006         
110007         me.headerCounter = 0;
110008         me.plugins = me.plugins || [];
110009
110010         // TODO: Pass in configurations to turn on/off dynamic
110011         //       resizing and disable resizing all together
110012
110013         // Only set up a Resizer and Reorderer for the topmost HeaderContainer.
110014         // Nested Group Headers are themselves HeaderContainers
110015         if (!me.isHeader) {
110016             me.resizer   = Ext.create('Ext.grid.plugin.HeaderResizer');
110017             me.reorderer = Ext.create('Ext.grid.plugin.HeaderReorderer');
110018             if (!me.enableColumnResize) {
110019                 me.resizer.disable();
110020             } 
110021             if (!me.enableColumnMove) {
110022                 me.reorderer.disable();
110023             }
110024             me.plugins.push(me.reorderer, me.resizer);
110025         }
110026
110027         // Base headers do not need a box layout
110028         if (me.isHeader && !me.items) {
110029             me.layout = 'auto';
110030         }
110031         // HeaderContainer and Group header needs a gridcolumn layout.
110032         else {
110033             me.layout = {
110034                 type: 'gridcolumn',
110035                 availableSpaceOffset: me.availableSpaceOffset,
110036                 align: 'stretchmax',
110037                 resetStretch: true
110038             };
110039         }
110040         me.defaults = me.defaults || {};
110041         Ext.applyIf(me.defaults, {
110042             width: me.defaultWidth,
110043             triStateSort: me.triStateSort,
110044             sortable: me.sortable
110045         });
110046         me.callParent();
110047         me.addEvents(
110048             /**
110049              * @event columnresize
110050              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
110051              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
110052              * @param {Number} width
110053              */
110054             'columnresize',
110055
110056             /**
110057              * @event headerclick
110058              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
110059              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
110060              * @param {Ext.EventObject} e
110061              * @param {HTMLElement} t
110062              */
110063             'headerclick',
110064
110065             /**
110066              * @event headertriggerclick
110067              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
110068              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
110069              * @param {Ext.EventObject} e
110070              * @param {HTMLElement} t
110071              */
110072             'headertriggerclick',
110073
110074             /**
110075              * @event columnmove
110076              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
110077              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
110078              * @param {Number} fromIdx
110079              * @param {Number} toIdx
110080              */
110081             'columnmove',
110082             /**
110083              * @event columnhide
110084              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
110085              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
110086              */
110087             'columnhide',
110088             /**
110089              * @event columnshow
110090              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
110091              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
110092              */
110093             'columnshow',
110094             /**
110095              * @event sortchange
110096              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
110097              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
110098              * @param {String} direction
110099              */
110100             'sortchange',
110101             /**
110102              * @event menucreate
110103              * Fired immediately after the column header menu is created.
110104              * @param {Ext.grid.header.Container} ct This instance
110105              * @param {Ext.menu.Menu} menu The Menu that was created
110106              */
110107             'menucreate'
110108         );
110109     },
110110
110111     onDestroy: function() {
110112         Ext.destroy(this.resizer, this.reorderer);
110113         this.callParent();
110114     },
110115
110116     // Invalidate column cache on add
110117     // We cannot refresh the View on every add because this method is called
110118     // when the HeaderDropZone moves Headers around, that will also refresh the view
110119     onAdd: function(c) {
110120         var me = this;
110121         if (!c.headerId) {
110122             c.headerId = 'h' + (++me.headerCounter);
110123         }
110124         me.callParent(arguments);
110125         me.purgeCache();
110126     },
110127
110128     // Invalidate column cache on remove
110129     // We cannot refresh the View on every remove because this method is called
110130     // when the HeaderDropZone moves Headers around, that will also refresh the view
110131     onRemove: function(c) {
110132         var me = this;
110133         me.callParent(arguments);
110134         me.purgeCache();
110135     },
110136
110137     afterRender: function() {
110138         this.callParent();
110139         var store   = this.up('[store]').store,
110140             sorters = store.sorters,
110141             first   = sorters.first(),
110142             hd;
110143
110144         if (first) {
110145             hd = this.down('gridcolumn[dataIndex=' + first.property  +']');
110146             if (hd) {
110147                 hd.setSortState(first.direction, false, true);
110148             }
110149         }
110150     },
110151
110152     afterLayout: function() {
110153         if (!this.isHeader) {
110154             var me = this,
110155                 topHeaders = me.query('>gridcolumn:not([hidden])'),
110156                 viewEl,
110157                 firstHeaderEl,
110158                 lastHeaderEl;
110159
110160             me.callParent(arguments);
110161
110162             if (topHeaders.length) {
110163                 firstHeaderEl = topHeaders[0].el;
110164                 if (firstHeaderEl !== me.pastFirstHeaderEl) {
110165                     if (me.pastFirstHeaderEl) {
110166                         me.pastFirstHeaderEl.removeCls(me.firstHeaderCls);
110167                     }
110168                     firstHeaderEl.addCls(me.firstHeaderCls);
110169                     me.pastFirstHeaderEl = firstHeaderEl;
110170                 }
110171                 
110172                 lastHeaderEl = topHeaders[topHeaders.length - 1].el;
110173                 if (lastHeaderEl !== me.pastLastHeaderEl) {
110174                     if (me.pastLastHeaderEl) {
110175                         me.pastLastHeaderEl.removeCls(me.lastHeaderCls);
110176                     }
110177                     lastHeaderEl.addCls(me.lastHeaderCls);
110178                     me.pastLastHeaderEl = lastHeaderEl
110179                 }
110180             }
110181         }
110182         
110183     },
110184
110185     onHeaderShow: function(header) {
110186         // Pass up to the GridSection
110187         var me = this,
110188             gridSection = me.ownerCt,
110189             menu = me.getMenu(),
110190             topItems, topItemsVisible,
110191             colCheckItem,
110192             itemToEnable,
110193             len, i;
110194
110195         if (menu) {
110196
110197             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
110198             if (colCheckItem) {
110199                 colCheckItem.setChecked(true, true);
110200             }
110201
110202             // There's more than one header visible, and we've disabled some checked items... re-enable them
110203             topItems = menu.query('#columnItem>menucheckitem[checked]');
110204             topItemsVisible = topItems.length;
110205             if ((me.getVisibleGridColumns().length > 1) && me.disabledMenuItems && me.disabledMenuItems.length) {
110206                 if (topItemsVisible == 1) {
110207                     Ext.Array.remove(me.disabledMenuItems, topItems[0]);
110208                 }
110209                 for (i = 0, len = me.disabledMenuItems.length; i < len; i++) {
110210                     itemToEnable = me.disabledMenuItems[i];
110211                     if (!itemToEnable.isDestroyed) {
110212                         itemToEnable[itemToEnable.menu ? 'enableCheckChange' : 'enable']();
110213                     }
110214                 }
110215                 if (topItemsVisible == 1) {
110216                     me.disabledMenuItems = topItems;
110217                 } else {
110218                     me.disabledMenuItems = [];
110219                 }
110220             }
110221         }
110222
110223         // Only update the grid UI when we are notified about base level Header shows;
110224         // Group header shows just cause a layout of the HeaderContainer
110225         if (!header.isGroupHeader) {
110226             if (me.view) {
110227                 me.view.onHeaderShow(me, header, true);
110228             }
110229             if (gridSection) {
110230                 gridSection.onHeaderShow(me, header);
110231             }
110232         }
110233         me.fireEvent('columnshow', me, header);
110234
110235         // The header's own hide suppresses cascading layouts, so lay the headers out now
110236         me.doLayout();
110237     },
110238
110239     onHeaderHide: function(header, suppressLayout) {
110240         // Pass up to the GridSection
110241         var me = this,
110242             gridSection = me.ownerCt,
110243             menu = me.getMenu(),
110244             colCheckItem;
110245
110246         if (menu) {
110247
110248             // If the header was hidden programmatically, sync the Menu state
110249             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
110250             if (colCheckItem) {
110251                 colCheckItem.setChecked(false, true);
110252             }
110253             me.setDisabledItems();
110254         }
110255
110256         // Only update the UI when we are notified about base level Header hides;
110257         if (!header.isGroupHeader) {
110258             if (me.view) {
110259                 me.view.onHeaderHide(me, header, true);
110260             }
110261             if (gridSection) {
110262                 gridSection.onHeaderHide(me, header);
110263             }
110264
110265             // The header's own hide suppresses cascading layouts, so lay the headers out now
110266             if (!suppressLayout) {
110267                 me.doLayout();
110268             }
110269         }
110270         me.fireEvent('columnhide', me, header);
110271     },
110272
110273     setDisabledItems: function(){
110274         var me = this,
110275             menu = me.getMenu(),
110276             i = 0,
110277             len,
110278             itemsToDisable,
110279             itemToDisable;
110280
110281         // Find what to disable. If only one top level item remaining checked, we have to disable stuff.
110282         itemsToDisable = menu.query('#columnItem>menucheckitem[checked]');
110283         if ((itemsToDisable.length === 1)) {
110284             if (!me.disabledMenuItems) {
110285                 me.disabledMenuItems = [];
110286             }
110287
110288             // If down to only one column visible, also disable any descendant checkitems
110289             if ((me.getVisibleGridColumns().length === 1) && itemsToDisable[0].menu) {
110290                 itemsToDisable = itemsToDisable.concat(itemsToDisable[0].menu.query('menucheckitem[checked]'));
110291             }
110292
110293             len = itemsToDisable.length;
110294             // Disable any further unchecking at any level.
110295             for (i = 0; i < len; i++) {
110296                 itemToDisable = itemsToDisable[i];
110297                 if (!Ext.Array.contains(me.disabledMenuItems, itemToDisable)) {
110298                     itemToDisable[itemToDisable.menu ? 'disableCheckChange' : 'disable']();
110299                     me.disabledMenuItems.push(itemToDisable);
110300                 }
110301             }
110302         }
110303     },
110304
110305     /**
110306      * Temporarily lock the headerCt. This makes it so that clicking on headers
110307      * don't trigger actions like sorting or opening of the header menu. This is
110308      * done because extraneous events may be fired on the headers after interacting
110309      * with a drag drop operation.
110310      * @private
110311      */
110312     tempLock: function() {
110313         this.ddLock = true;
110314         Ext.Function.defer(function() {
110315             this.ddLock = false;
110316         }, 200, this);
110317     },
110318
110319     onHeaderResize: function(header, w, suppressFocus) {
110320         this.tempLock();
110321         if (this.view && this.view.rendered) {
110322             this.view.onHeaderResize(header, w, suppressFocus);
110323         }
110324         this.fireEvent('columnresize', this, header, w);
110325     },
110326
110327     onHeaderClick: function(header, e, t) {
110328         this.fireEvent("headerclick", this, header, e, t);
110329     },
110330
110331     onHeaderTriggerClick: function(header, e, t) {
110332         // generate and cache menu, provide ability to cancel/etc
110333         if (this.fireEvent("headertriggerclick", this, header, e, t) !== false) {
110334             this.showMenuBy(t, header);
110335         }
110336     },
110337
110338     showMenuBy: function(t, header) {
110339         var menu = this.getMenu(),
110340             ascItem  = menu.down('#ascItem'),
110341             descItem = menu.down('#descItem'),
110342             sortableMth;
110343
110344         menu.activeHeader = menu.ownerCt = header;
110345         menu.setFloatParent(header);
110346         // TODO: remove coupling to Header's titleContainer el
110347         header.titleContainer.addCls(this.headerOpenCls);
110348
110349         // enable or disable asc & desc menu items based on header being sortable
110350         sortableMth = header.sortable ? 'enable' : 'disable';
110351         if (ascItem) {
110352             ascItem[sortableMth]();
110353         }
110354         if (descItem) {
110355             descItem[sortableMth]();
110356         }
110357         menu.showBy(t);
110358     },
110359
110360     // remove the trigger open class when the menu is hidden
110361     onMenuDeactivate: function() {
110362         var menu = this.getMenu();
110363         // TODO: remove coupling to Header's titleContainer el
110364         menu.activeHeader.titleContainer.removeCls(this.headerOpenCls);
110365     },
110366
110367     moveHeader: function(fromIdx, toIdx) {
110368
110369         // An automatically expiring lock
110370         this.tempLock();
110371         this.onHeaderMoved(this.move(fromIdx, toIdx), fromIdx, toIdx);
110372     },
110373
110374     purgeCache: function() {
110375         var me = this;
110376         // Delete column cache - column order has changed.
110377         delete me.gridDataColumns;
110378
110379         // Menu changes when columns are moved. It will be recreated.
110380         if (me.menu) {
110381             me.menu.destroy();
110382             delete me.menu;
110383         }
110384     },
110385
110386     onHeaderMoved: function(header, fromIdx, toIdx) {
110387         var me = this,
110388             gridSection = me.ownerCt;
110389
110390         if (gridSection) {
110391             gridSection.onHeaderMove(me, header, fromIdx, toIdx);
110392         }
110393         me.fireEvent("columnmove", me, header, fromIdx, toIdx);
110394     },
110395
110396     /**
110397      * Gets the menu (and will create it if it doesn't already exist)
110398      * @private
110399      */
110400     getMenu: function() {
110401         var me = this;
110402
110403         if (!me.menu) {
110404             me.menu = Ext.create('Ext.menu.Menu', {
110405                 items: me.getMenuItems(),
110406                 listeners: {
110407                     deactivate: me.onMenuDeactivate,
110408                     scope: me
110409                 }
110410             });
110411             me.setDisabledItems();
110412             me.fireEvent('menucreate', me, me.menu);
110413         }
110414         return me.menu;
110415     },
110416
110417     /**
110418      * Returns an array of menu items to be placed into the shared menu
110419      * across all headers in this header container.
110420      * @returns {Array} menuItems
110421      */
110422     getMenuItems: function() {
110423         var me = this,
110424             menuItems = [{
110425                 itemId: 'columnItem',
110426                 text: me.columnsText,
110427                 cls: Ext.baseCSSPrefix + 'cols-icon',
110428                 menu: me.getColumnMenu(me)
110429             }];
110430
110431         if (me.sortable) {
110432             menuItems.unshift({
110433                 itemId: 'ascItem',
110434                 text: me.sortAscText,
110435                 cls: 'xg-hmenu-sort-asc',
110436                 handler: me.onSortAscClick,
110437                 scope: me
110438             },{
110439                 itemId: 'descItem',
110440                 text: me.sortDescText,
110441                 cls: 'xg-hmenu-sort-desc',
110442                 handler: me.onSortDescClick,
110443                 scope: me
110444             },'-');
110445         }
110446         return menuItems;
110447     },
110448
110449     // sort asc when clicking on item in menu
110450     onSortAscClick: function() {
110451         var menu = this.getMenu(),
110452             activeHeader = menu.activeHeader;
110453
110454         activeHeader.setSortState('ASC');
110455     },
110456
110457     // sort desc when clicking on item in menu
110458     onSortDescClick: function() {
110459         var menu = this.getMenu(),
110460             activeHeader = menu.activeHeader;
110461
110462         activeHeader.setSortState('DESC');
110463     },
110464
110465     /**
110466      * Returns an array of menu CheckItems corresponding to all immediate children of the passed Container which have been configured as hideable.
110467      */
110468     getColumnMenu: function(headerContainer) {
110469         var menuItems = [],
110470             i = 0,
110471             item,
110472             items = headerContainer.query('>gridcolumn[hideable]'),
110473             itemsLn = items.length,
110474             menuItem;
110475
110476         for (; i < itemsLn; i++) {
110477             item = items[i];
110478             menuItem = Ext.create('Ext.menu.CheckItem', {
110479                 text: item.text,
110480                 checked: !item.hidden,
110481                 hideOnClick: false,
110482                 headerId: item.id,
110483                 menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
110484                 checkHandler: this.onColumnCheckChange,
110485                 scope: this
110486             });
110487             if (itemsLn === 1) {
110488                 menuItem.disabled = true;
110489             }
110490             menuItems.push(menuItem);
110491
110492             // If the header is ever destroyed - for instance by dragging out the last remaining sub header,
110493             // then the associated menu item must also be destroyed.
110494             item.on({
110495                 destroy: Ext.Function.bind(menuItem.destroy, menuItem)
110496             });
110497         }
110498         return menuItems;
110499     },
110500
110501     onColumnCheckChange: function(checkItem, checked) {
110502         var header = Ext.getCmp(checkItem.headerId);
110503         header[checked ? 'show' : 'hide']();
110504     },
110505
110506     /**
110507      * Get the columns used for generating a template via TableChunker.
110508      * Returns an array of all columns and their
110509      *  - dataIndex
110510      *  - align
110511      *  - width
110512      *  - id
110513      *  - columnId - used to create an identifying CSS class
110514      *  - cls The tdCls configuration from the Column object
110515      *  @private
110516      */
110517     getColumnsForTpl: function(flushCache) {
110518         var cols    = [],
110519             headers   = this.getGridColumns(flushCache),
110520             headersLn = headers.length,
110521             i = 0,
110522             header;
110523
110524         for (; i < headersLn; i++) {
110525             header = headers[i];
110526             cols.push({
110527                 dataIndex: header.dataIndex,
110528                 align: header.align,
110529                 width: header.hidden ? 0 : header.getDesiredWidth(),
110530                 id: header.id,
110531                 cls: header.tdCls,
110532                 columnId: header.getItemId()
110533             });
110534         }
110535         return cols;
110536     },
110537
110538     /**
110539      * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
110540      * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
110541      */
110542     getColumnCount: function() {
110543         return this.getGridColumns().length;
110544     },
110545
110546     /**
110547      * Gets the full width of all columns that are visible.
110548      */
110549     getFullWidth: function(flushCache) {
110550         var fullWidth = 0,
110551             headers     = this.getVisibleGridColumns(flushCache),
110552             headersLn   = headers.length,
110553             i         = 0;
110554
110555         for (; i < headersLn; i++) {
110556             if (!isNaN(headers[i].width)) {
110557                 // use headers getDesiredWidth if its there
110558                 if (headers[i].getDesiredWidth) {
110559                     fullWidth += headers[i].getDesiredWidth();
110560                 // if injected a diff cmp use getWidth
110561                 } else {
110562                     fullWidth += headers[i].getWidth();
110563                 }
110564             }
110565         }
110566         return fullWidth;
110567     },
110568
110569     // invoked internally by a header when not using triStateSorting
110570     clearOtherSortStates: function(activeHeader) {
110571         var headers   = this.getGridColumns(),
110572             headersLn = headers.length,
110573             i         = 0,
110574             oldSortState;
110575
110576         for (; i < headersLn; i++) {
110577             if (headers[i] !== activeHeader) {
110578                 oldSortState = headers[i].sortState;
110579                 // unset the sortstate and dont recurse
110580                 headers[i].setSortState(null, true);
110581                 //if (!silent && oldSortState !== null) {
110582                 //    this.fireEvent('sortchange', this, headers[i], null);
110583                 //}
110584             }
110585         }
110586     },
110587
110588     /**
110589      * Returns an array of the <b>visible<b> columns in the grid. This goes down to the lowest column header
110590      * level, and does not return <i>grouped</i> headers which contain sub headers.
110591      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
110592      * @returns {Array}
110593      */
110594     getVisibleGridColumns: function(refreshCache) {
110595         return Ext.ComponentQuery.query(':not([hidden])', this.getGridColumns(refreshCache));
110596     },
110597
110598     /**
110599      * Returns an array of all columns which map to Store fields. This goes down to the lowest column header
110600      * level, and does not return <i>grouped</i> headers which contain sub headers.
110601      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
110602      * @returns {Array}
110603      */
110604     getGridColumns: function(refreshCache) {
110605         var me = this,
110606             result = refreshCache ? null : me.gridDataColumns;
110607
110608         // Not already got the column cache, so collect the base columns
110609         if (!result) {
110610             me.gridDataColumns = result = [];
110611             me.cascade(function(c) {
110612                 if ((c !== me) && !c.isGroupHeader) {
110613                     result.push(c);
110614                 }
110615             });
110616         }
110617
110618         return result;
110619     },
110620
110621     /**
110622      * Get the index of a leaf level header regardless of what the nesting
110623      * structure is.
110624      */
110625     getHeaderIndex: function(header) {
110626         var columns = this.getGridColumns();
110627         return Ext.Array.indexOf(columns, header);
110628     },
110629
110630     /**
110631      * Get a leaf level header by index regardless of what the nesting
110632      * structure is.
110633      */
110634     getHeaderAtIndex: function(index) {
110635         var columns = this.getGridColumns();
110636         return columns[index];
110637     },
110638
110639     /**
110640      * Maps the record data to base it on the header id's.
110641      * This correlates to the markup/template generated by
110642      * TableChunker.
110643      */
110644     prepareData: function(data, rowIdx, record, view, panel) {
110645         var obj       = {},
110646             headers   = this.gridDataColumns || this.getGridColumns(),
110647             headersLn = headers.length,
110648             colIdx    = 0,
110649             header,
110650             headerId,
110651             renderer,
110652             value,
110653             metaData,
110654             store = panel.store;
110655
110656         for (; colIdx < headersLn; colIdx++) {
110657             metaData = {
110658                 tdCls: '',
110659                 style: ''
110660             };
110661             header = headers[colIdx];
110662             headerId = header.id;
110663             renderer = header.renderer;
110664             value = data[header.dataIndex];
110665
110666             // When specifying a renderer as a string, it always resolves
110667             // to Ext.util.Format
110668             if (typeof renderer === "string") {
110669                 header.renderer = renderer = Ext.util.Format[renderer];
110670             }
110671             
110672             if (typeof renderer === "function") {
110673                 value = renderer.call(
110674                     header.scope || this.ownerCt,
110675                     value,
110676                     // metadata per cell passing an obj by reference so that
110677                     // it can be manipulated inside the renderer
110678                     metaData,
110679                     record,
110680                     rowIdx,
110681                     colIdx,
110682                     store,
110683                     view
110684                 );
110685             }
110686
110687             if (metaData.css) {
110688                 // This warning attribute is used by the compat layer
110689                 obj.cssWarning = true;
110690                 metaData.tdCls = metaData.css;
110691                 delete metaData.css;
110692             }
110693             
110694             obj[headerId+'-modified'] = record.modified[header.dataIndex] ? Ext.baseCSSPrefix + 'grid-dirty-cell' : '';
110695             obj[headerId+'-tdCls'] = metaData.tdCls;
110696             obj[headerId+'-tdAttr'] = metaData.tdAttr;
110697             obj[headerId+'-style'] = metaData.style;
110698             if (value === undefined || value === null || value === '') {
110699                 value = '&#160;';
110700             }
110701             obj[headerId] = value;
110702         }
110703         return obj;
110704     },
110705
110706     expandToFit: function(header) {
110707         if (this.view) {
110708             this.view.expandToFit(header);
110709         }
110710     }
110711 });
110712
110713 /**
110714  * @class Ext.grid.column.Column
110715  * @extends Ext.grid.header.Container
110716  * 
110717  * This class specifies the definition for a column inside a {@link Ext.grid.Panel}. It encompasses
110718  * both the grid header configuration as well as displaying data within the grid itself. If the
110719  * {@link #columns} configuration is specified, this column will become a column group and can
110720  * container other columns inside. In general, this class will not be created directly, rather
110721  * an array of column configurations will be passed to the grid:
110722  * 
110723  * {@img Ext.grid.column.Column/Ext.grid.column.Column.png Ext.grid.column.Column grid column}
110724  *
110725  * ## Code
110726  *
110727  *     Ext.create('Ext.data.Store', {
110728  *         storeId:'employeeStore',
110729  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
110730  *         data:[
110731  *             {firstname:"Michael", lastname:"Scott", senority:7, dep:"Manangement", hired:"01/10/2004"},
110732  *             {firstname:"Dwight", lastname:"Schrute", senority:2, dep:"Sales", hired:"04/01/2004"},
110733  *             {firstname:"Jim", lastname:"Halpert", senority:3, dep:"Sales", hired:"02/22/2006"},
110734  *             {firstname:"Kevin", lastname:"Malone", senority:4, dep:"Accounting", hired:"06/10/2007"},
110735  *             {firstname:"Angela", lastname:"Martin", senority:5, dep:"Accounting", hired:"10/21/2008"}                        
110736  *         ]
110737  *     });
110738  *     
110739  *     Ext.create('Ext.grid.Panel', {
110740  *         title: 'Column Demo',
110741  *         store: Ext.data.StoreManager.lookup('employeeStore'),
110742  *         columns: [
110743  *             {text: 'First Name',  dataIndex:'firstname'},
110744  *             {text: 'Last Name',  dataIndex:'lastname'},
110745  *             {text: 'Hired Month',  dataIndex:'hired', xtype:'datecolumn', format:'M'},              
110746  *             {text: 'Deparment (Yrs)', xtype:'templatecolumn', tpl:'{dep} ({senority})'}
110747  *         ],
110748  *         width: 400,
110749  *         renderTo: Ext.getBody()
110750  *     });
110751  *     
110752  * ## Convenience Subclasses
110753  * There are several column subclasses that provide default rendering for various data types
110754  *
110755  *  - {@link Ext.grid.column.Action}: Renders icons that can respond to click events inline
110756  *  - {@link Ext.grid.column.Boolean}: Renders for boolean values 
110757  *  - {@link Ext.grid.column.Date}: Renders for date values
110758  *  - {@link Ext.grid.column.Number}: Renders for numeric values
110759  *  - {@link Ext.grid.column.Template}: Renders a value using an {@link Ext.XTemplate} using the record data 
110760  * 
110761  * ## Setting Sizes
110762  * The columns are laid out by a {@link Ext.layout.container.HBox} layout, so a column can either
110763  * be given an explicit width value or a flex configuration. If no width is specified the grid will
110764  * automatically the size the column to 100px. For column groups, the size is calculated by measuring
110765  * the width of the child columns, so a width option should not be specified in that case.
110766  * 
110767  * ## Header Options
110768  *  - {@link #text}: Sets the header text for the column
110769  *  - {@link #sortable}: Specifies whether the column can be sorted by clicking the header or using the column menu
110770  *  - {@link #hideable}: Specifies whether the column can be hidden using the column menu
110771  *  - {@link #menuDisabled}: Disables the column header menu
110772  *  - {@link #draggable}: Specifies whether the column header can be reordered by dragging
110773  *  - {@link #groupable}: Specifies whether the grid can be grouped by the column dataIndex. See also {@link Ext.grid.feature.Grouping}
110774  * 
110775  * ## Data Options
110776  *  - {@link #dataIndex}: The dataIndex is the field in the underlying {@link Ext.data.Store} to use as the value for the column.
110777  *  - {@link #renderer}: Allows the underlying store value to be transformed before being displayed in the grid
110778  * 
110779  * @xtype gridcolumn
110780  */
110781 Ext.define('Ext.grid.column.Column', {
110782     extend: 'Ext.grid.header.Container',
110783     alias: 'widget.gridcolumn',
110784     requires: ['Ext.util.KeyNav'],
110785     alternateClassName: 'Ext.grid.Column',
110786
110787     baseCls: Ext.baseCSSPrefix + 'column-header ' + Ext.baseCSSPrefix + 'unselectable',
110788
110789     // Not the standard, automatically applied overCls because we must filter out overs of child headers.
110790     hoverCls: Ext.baseCSSPrefix + 'column-header-over',
110791
110792     handleWidth: 5,
110793
110794     sortState: null,
110795
110796     possibleSortStates: ['ASC', 'DESC'],
110797
110798     renderTpl:
110799         '<div class="' + Ext.baseCSSPrefix + 'column-header-inner">' +
110800             '<span class="' + Ext.baseCSSPrefix + 'column-header-text">' +
110801                 '{text}' +
110802             '</span>' +
110803             '<tpl if="!values.menuDisabled"><div class="' + Ext.baseCSSPrefix + 'column-header-trigger"></div></tpl>' +
110804         '</div>',
110805
110806     /**
110807      * @cfg {Array} columns
110808      * <p>An optional array of sub-column definitions. This column becomes a group, and houses the columns defined in the <code>columns</code> config.</p>
110809      * <p>Group columns may not be sortable. But they may be hideable and moveable. And you may move headers into and out of a group. Note that
110810      * if all sub columns are dragged out of a group, the group is destroyed.
110811      */
110812
110813     /**
110814      * @cfg {String} dataIndex <p><b>Required</b>. The name of the field in the
110815      * grid's {@link Ext.data.Store}'s {@link Ext.data.Model} definition from
110816      * which to draw the column's value.</p>
110817      */
110818     dataIndex: null,
110819
110820     /**
110821      * @cfg {String} text Optional. The header text to be used as innerHTML
110822      * (html tags are accepted) to display in the Grid.  <b>Note</b>: to
110823      * have a clickable header with no text displayed you can use the
110824      * default of <tt>'&#160;'</tt>.
110825      */
110826     text: '&#160',
110827
110828     /**
110829      * @cfg {Boolean} sortable Optional. <tt>true</tt> if sorting is to be allowed on this column.
110830      * Whether local/remote sorting is used is specified in <code>{@link Ext.data.Store#remoteSort}</code>.
110831      */
110832     sortable: true,
110833     
110834     /**
110835      * @cfg {Boolean} groupable Optional. If the grid uses a {@link Ext.grid.feature.Grouping}, this option
110836      * may be used to disable the header menu item to group by the column selected. By default,
110837      * the header menu group option is enabled. Set to false to disable (but still show) the
110838      * group option in the header menu for the column.
110839      */
110840      
110841     /**
110842      * @cfg {Boolean} hideable Optional. Specify as <tt>false</tt> to prevent the user from hiding this column
110843      * (defaults to true).
110844      */
110845     hideable: true,
110846
110847     /**
110848      * @cfg {Boolean} menuDisabled
110849      * True to disabled the column header menu containing sort/hide options. Defaults to false.
110850      */
110851     menuDisabled: false,
110852
110853     /**
110854      * @cfg {Function} renderer
110855      * <p>A renderer is an 'interceptor' method which can be used transform data (value, appearance, etc.) before it
110856      * is rendered. Example:</p>
110857      * <pre><code>{
110858     renderer: function(value){
110859         if (value === 1) {
110860             return '1 person';
110861         }
110862         return value + ' people';
110863     }
110864 }
110865      * </code></pre>
110866      * @param {Mixed} value The data value for the current cell
110867      * @param {Object} metaData A collection of metadata about the current cell; can be used or modified by
110868      * the renderer. Recognized properties are: <tt>tdCls</tt>, <tt>tdAttr</tt>, and <tt>style</tt>.
110869      * @param {Ext.data.Model} record The record for the current row
110870      * @param {Number} rowIndex The index of the current row
110871      * @param {Number} colIndex The index of the current column
110872      * @param {Ext.data.Store} store The data store
110873      * @param {Ext.view.View} view The current view
110874      * @return {String} The HTML to be rendered
110875      */
110876     renderer: false,
110877
110878     /**
110879      * @cfg {String} align Sets the alignment of the header and rendered columns.
110880      * Defaults to 'left'.
110881      */
110882     align: 'left',
110883
110884     /**
110885      * @cfg {Boolean} draggable Indicates whether or not the header can be drag and drop re-ordered.
110886      * Defaults to true.
110887      */
110888     draggable: true,
110889
110890     // Header does not use the typical ComponentDraggable class and therefore we
110891     // override this with an emptyFn. It is controlled at the HeaderDragZone.
110892     initDraggable: Ext.emptyFn,
110893
110894     /**
110895      * @cfg {String} tdCls <p>Optional. A CSS class names to apply to the table cells for this column.</p>
110896      */
110897
110898     /**
110899      * @property {Ext.core.Element} triggerEl
110900      */
110901
110902     /**
110903      * @property {Ext.core.Element} textEl
110904      */
110905
110906     /**
110907      * @private
110908      * Set in this class to identify, at runtime, instances which are not instances of the
110909      * HeaderContainer base class, but are in fact, the subclass: Header.
110910      */
110911     isHeader: true,
110912
110913     initComponent: function() {
110914         var me = this,
110915             i,
110916             len;
110917         
110918         if (Ext.isDefined(me.header)) {
110919             me.text = me.header;
110920             delete me.header;
110921         }
110922
110923         // Flexed Headers need to have a minWidth defined so that they can never be squeezed out of existence by the
110924         // HeaderContainer's specialized Box layout, the ColumnLayout. The ColumnLayout's overridden calculateChildboxes
110925         // method extends the available layout space to accommodate the "desiredWidth" of all the columns.
110926         if (me.flex) {
110927             me.minWidth = me.minWidth || Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
110928         }
110929         // Non-flexed Headers may never be squeezed in the event of a shortfall so
110930         // always set their minWidth to their current width.
110931         else {
110932             me.minWidth = me.width;
110933         }
110934
110935         if (!me.triStateSort) {
110936             me.possibleSortStates.length = 2;
110937         }
110938
110939         // A group header; It contains items which are themselves Headers
110940         if (Ext.isDefined(me.columns)) {
110941             me.isGroupHeader = true;
110942
110943             if (me.dataIndex) {
110944                 Ext.Error.raise('Ext.grid.column.Column: Group header may not accept a dataIndex');
110945             }
110946             if ((me.width && me.width !== Ext.grid.header.Container.prototype.defaultWidth) || me.flex) {
110947                 Ext.Error.raise('Ext.grid.column.Column: Group header does not support setting explicit widths or flexs. The group header width is calculated by the sum of its children.');
110948             }
110949
110950             // The headers become child items
110951             me.items = me.columns;
110952             delete me.columns;
110953             delete me.flex;
110954             me.width = 0;
110955
110956             // Acquire initial width from sub headers
110957             for (i = 0, len = me.items.length; i < len; i++) {
110958                 me.width += me.items[i].width || Ext.grid.header.Container.prototype.defaultWidth;
110959                 if (me.items[i].flex) {
110960                     Ext.Error.raise('Ext.grid.column.Column: items of a grouped header do not support flexed values. Each item must explicitly define its width.');
110961                 }
110962             }
110963             me.minWidth = me.width;
110964
110965             me.cls = (me.cls||'') + ' ' + Ext.baseCSSPrefix + 'group-header';
110966             me.sortable = false;
110967             me.fixed = true;
110968             me.align = 'center';
110969         }
110970
110971         Ext.applyIf(me.renderSelectors, {
110972             titleContainer: '.' + Ext.baseCSSPrefix + 'column-header-inner',
110973             triggerEl: '.' + Ext.baseCSSPrefix + 'column-header-trigger',
110974             textEl: '.' + Ext.baseCSSPrefix + 'column-header-text'
110975         });
110976
110977         // Initialize as a HeaderContainer
110978         me.callParent(arguments);
110979     },
110980
110981     onAdd: function(childHeader) {
110982         childHeader.isSubHeader = true;
110983         childHeader.addCls(Ext.baseCSSPrefix + 'group-sub-header');
110984     },
110985
110986     onRemove: function(childHeader) {
110987         childHeader.isSubHeader = false;
110988         childHeader.removeCls(Ext.baseCSSPrefix + 'group-sub-header');
110989     },
110990
110991     initRenderData: function() {
110992         var me = this;
110993         
110994         Ext.applyIf(me.renderData, {
110995             text: me.text,
110996             menuDisabled: me.menuDisabled
110997         });
110998         return me.callParent(arguments);
110999     },
111000
111001     // note that this should invalidate the menu cache
111002     setText: function(text) {
111003         this.text = text;
111004         if (this.rendered) {
111005             this.textEl.update(text);
111006         } 
111007     },
111008
111009     // Find the topmost HeaderContainer: An ancestor which is NOT a Header.
111010     // Group Headers are themselves HeaderContainers
111011     getOwnerHeaderCt: function() {
111012         return this.up(':not([isHeader])');
111013     },
111014
111015     /**
111016      * Returns the true grid column index assiciated with this Column only if this column is a base level Column.
111017      * If it is a group column, it returns <code>false</code>
111018      */
111019     getIndex: function() {
111020         return this.isGroupColumn ? false : this.getOwnerHeaderCt().getHeaderIndex(this);
111021     },
111022
111023     afterRender: function() {
111024         var me = this,
111025             el = me.el;
111026
111027         me.callParent(arguments);
111028
111029         el.addCls(Ext.baseCSSPrefix + 'column-header-align-' + me.align).addClsOnOver(me.overCls);
111030
111031         me.mon(el, {
111032             click:     me.onElClick,
111033             dblclick:  me.onElDblClick,
111034             scope:     me
111035         });
111036         
111037         // BrowserBug: Ie8 Strict Mode, this will break the focus for this browser,
111038         // must be fixed when focus management will be implemented.
111039         if (!Ext.isIE8 || !Ext.isStrict) {
111040             me.mon(me.getFocusEl(), {
111041                 focus: me.onTitleMouseOver,
111042                 blur: me.onTitleMouseOut,
111043                 scope: me
111044             });
111045         }
111046
111047         me.mon(me.titleContainer, {
111048             mouseenter:  me.onTitleMouseOver,
111049             mouseleave:  me.onTitleMouseOut,
111050             scope:      me
111051         });
111052
111053         me.keyNav = Ext.create('Ext.util.KeyNav', el, {
111054             enter: me.onEnterKey,
111055             down: me.onDownKey,
111056             scope: me
111057         });
111058     },
111059
111060     setSize: function(width, height) {
111061         var me = this,
111062             headerCt = me.ownerCt,
111063             ownerHeaderCt = me.getOwnerHeaderCt(),
111064             siblings,
111065             len, i,
111066             oldWidth = me.getWidth(),
111067             newWidth = 0;
111068
111069         if (width !== oldWidth) {
111070
111071             // Bubble size changes upwards to group headers
111072             if (headerCt.isGroupHeader) {
111073
111074                 siblings = headerCt.items.items;
111075                 len = siblings.length;
111076
111077                 // Size the owning group to the size of its sub headers 
111078                 if (siblings[len - 1].rendered) {
111079
111080                     for (i = 0; i < len; i++) {
111081                         newWidth += (siblings[i] === me) ? width : siblings[i].getWidth();
111082                     }
111083                     headerCt.minWidth = newWidth;
111084                     headerCt.setWidth(newWidth);
111085                 }
111086             }
111087             me.callParent(arguments);
111088         }
111089     },
111090
111091     afterComponentLayout: function(width, height) {
111092         var me = this,
111093             ownerHeaderCt = this.getOwnerHeaderCt();
111094
111095         me.callParent(arguments);
111096
111097         // Only changes at the base level inform the grid's HeaderContainer which will update the View
111098         // Skip this if the width is null or undefined which will be the Box layout's initial pass  through the child Components
111099         // Skip this if it's the initial size setting in which case there is no ownerheaderCt yet - that is set afterRender
111100         if (width && !me.isGroupHeader && ownerHeaderCt) {
111101             ownerHeaderCt.onHeaderResize(me, width, true);
111102         }
111103     },
111104
111105     // private
111106     // After the container has laid out and stretched, it calls this to correctly pad the inner to center the text vertically
111107     setPadding: function() {
111108         var me = this,
111109             headerHeight,
111110             lineHeight = parseInt(me.textEl.getStyle('line-height'), 10);
111111
111112         // Top title containing element must stretch to match height of sibling group headers
111113         if (!me.isGroupHeader) {
111114             headerHeight = me.el.getViewSize().height;
111115             if (me.titleContainer.getHeight() < headerHeight) {
111116                 me.titleContainer.dom.style.height = headerHeight + 'px';
111117             }
111118         }
111119         headerHeight = me.titleContainer.getViewSize().height;
111120
111121         // Vertically center the header text in potentially vertically stretched header
111122         if (lineHeight) {
111123             me.titleContainer.setStyle({
111124                 paddingTop: Math.max(((headerHeight - lineHeight) / 2), 0) + 'px'
111125             });
111126         }
111127
111128         // Only IE needs this
111129         if (Ext.isIE && me.triggerEl) {
111130             me.triggerEl.setHeight(headerHeight);
111131         }
111132     },
111133
111134     onDestroy: function() {
111135         var me = this;
111136         Ext.destroy(me.keyNav);
111137         delete me.keyNav;
111138         me.callParent(arguments);
111139     },
111140
111141     onTitleMouseOver: function() {
111142         this.titleContainer.addCls(this.hoverCls);
111143     },
111144
111145     onTitleMouseOut: function() {
111146         this.titleContainer.removeCls(this.hoverCls);
111147     },
111148
111149     onDownKey: function(e) {
111150         if (this.triggerEl) {
111151             this.onElClick(e, this.triggerEl.dom || this.el.dom);
111152         }
111153     },
111154
111155     onEnterKey: function(e) {
111156         this.onElClick(e, this.el.dom);
111157     },
111158
111159     /**
111160      * @private
111161      * Double click 
111162      * @param e
111163      * @param t
111164      */
111165     onElDblClick: function(e, t) {
111166         var me = this,
111167             ownerCt = me.ownerCt;
111168         if (ownerCt && Ext.Array.indexOf(ownerCt.items, me) !== 0 && me.isOnLeftEdge(e) ) {
111169             ownerCt.expandToFit(me.previousSibling('gridcolumn'));
111170         }
111171     },
111172
111173     onElClick: function(e, t) {
111174
111175         // The grid's docked HeaderContainer.
111176         var me = this,
111177             ownerHeaderCt = me.getOwnerHeaderCt();
111178
111179         if (ownerHeaderCt && !ownerHeaderCt.ddLock) {
111180             // Firefox doesn't check the current target in a within check.
111181             // Therefore we check the target directly and then within (ancestors)
111182             if (me.triggerEl && (e.target === me.triggerEl.dom || t === me.triggerEl.dom || e.within(me.triggerEl))) {
111183                 ownerHeaderCt.onHeaderTriggerClick(me, e, t);
111184             // if its not on the left hand edge, sort
111185             } else if (e.getKey() || (!me.isOnLeftEdge(e) && !me.isOnRightEdge(e))) {
111186                 me.toggleSortState();
111187                 ownerHeaderCt.onHeaderClick(me, e, t);
111188             }
111189         }
111190     },
111191
111192     /**
111193      * @private
111194      * Process UI events from the view. The owning TablePanel calls this method, relaying events from the TableView
111195      * @param {String} type Event type, eg 'click'
111196      * @param {TableView} view TableView Component
111197      * @param {HtmlElement} cell Cell HtmlElement the event took place within
111198      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
111199      * @param {Number} cellIndex Cell index within the row
111200      * @param {EventObject} e Original event
111201      */
111202     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
111203         return this.fireEvent.apply(this, arguments);
111204     },
111205
111206     toggleSortState: function() {
111207         var me = this,
111208             idx,
111209             nextIdx;
111210             
111211         if (me.sortable) {
111212             idx = Ext.Array.indexOf(me.possibleSortStates, me.sortState);
111213
111214             nextIdx = (idx + 1) % me.possibleSortStates.length;
111215             me.setSortState(me.possibleSortStates[nextIdx]);
111216         }
111217     },
111218
111219     doSort: function(state) {
111220         var ds = this.up('tablepanel').store;
111221         ds.sort({
111222             property: this.getSortParam(),
111223             direction: state
111224         });
111225     },
111226
111227     /**
111228      * Returns the parameter to sort upon when sorting this header. By default
111229      * this returns the dataIndex and will not need to be overriden in most cases.
111230      */
111231     getSortParam: function() {
111232         return this.dataIndex;
111233     },
111234
111235     //setSortState: function(state, updateUI) {
111236     //setSortState: function(state, doSort) {
111237     setSortState: function(state, skipClear, initial) {
111238         var me = this,
111239             colSortClsPrefix = Ext.baseCSSPrefix + 'column-header-sort-',
111240             ascCls = colSortClsPrefix + 'ASC',
111241             descCls = colSortClsPrefix + 'DESC',
111242             nullCls = colSortClsPrefix + 'null',
111243             ownerHeaderCt = me.getOwnerHeaderCt(),
111244             oldSortState = me.sortState;
111245
111246         if (oldSortState !== state && me.getSortParam()) {
111247             me.addCls(colSortClsPrefix + state);
111248             // don't trigger a sort on the first time, we just want to update the UI
111249             if (state && !initial) {
111250                 me.doSort(state);
111251             }
111252             switch (state) {
111253                 case 'DESC':
111254                     me.removeCls([ascCls, nullCls]);
111255                     break;
111256                 case 'ASC':
111257                     me.removeCls([descCls, nullCls]);
111258                     break;
111259                 case null:
111260                     me.removeCls([ascCls, descCls]);
111261                     break;
111262             }
111263             if (ownerHeaderCt && !me.triStateSort && !skipClear) {
111264                 ownerHeaderCt.clearOtherSortStates(me);
111265             }
111266             me.sortState = state;
111267             ownerHeaderCt.fireEvent('sortchange', ownerHeaderCt, me, state);
111268         }
111269     },
111270
111271     hide: function() {
111272         var me = this,
111273             items,
111274             len, i,
111275             lb,
111276             newWidth = 0,
111277             ownerHeaderCt = me.getOwnerHeaderCt();
111278
111279         // Hiding means setting to zero width, so cache the width
111280         me.oldWidth = me.getWidth();
111281
111282         // Hiding a group header hides itself, and then informs the HeaderContainer about its sub headers (Suppressing header layout)
111283         if (me.isGroupHeader) {
111284             items = me.items.items;
111285             me.callParent(arguments);
111286             ownerHeaderCt.onHeaderHide(me);
111287             for (i = 0, len = items.length; i < len; i++) {
111288                 items[i].hidden = true;
111289                 ownerHeaderCt.onHeaderHide(items[i], true);
111290             }
111291             return;
111292         }
111293
111294         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
111295         lb = me.ownerCt.componentLayout.layoutBusy;
111296         me.ownerCt.componentLayout.layoutBusy = true;
111297         me.callParent(arguments);
111298         me.ownerCt.componentLayout.layoutBusy = lb;
111299
111300         // Notify owning HeaderContainer
111301         ownerHeaderCt.onHeaderHide(me);
111302
111303         if (me.ownerCt.isGroupHeader) {
111304             // If we've just hidden the last header in a group, then hide the group
111305             items = me.ownerCt.query('>:not([hidden])');
111306             if (!items.length) {
111307                 me.ownerCt.hide();
111308             }
111309             // Size the group down to accommodate fewer sub headers
111310             else {
111311                 for (i = 0, len = items.length; i < len; i++) {
111312                     newWidth += items[i].getWidth();
111313                 }
111314                 me.ownerCt.minWidth = newWidth;
111315                 me.ownerCt.setWidth(newWidth);
111316             }
111317         }
111318     },
111319
111320     show: function() {
111321         var me = this,
111322             ownerCt = me.getOwnerHeaderCt(),
111323             lb,
111324             items,
111325             len, i,
111326             newWidth = 0;
111327
111328         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
111329         lb = me.ownerCt.componentLayout.layoutBusy;
111330         me.ownerCt.componentLayout.layoutBusy = true;
111331         me.callParent(arguments);
111332         me.ownerCt.componentLayout.layoutBusy = lb;
111333
111334         // If a sub header, ensure that the group header is visible
111335         if (me.isSubHeader) {
111336             if (!me.ownerCt.isVisible()) {
111337                 me.ownerCt.show();
111338             }
111339         }
111340
111341         // If we've just shown a group with all its sub headers hidden, then show all its sub headers
111342         if (me.isGroupHeader && !me.query(':not([hidden])').length) {
111343             items = me.query('>*');
111344             for (i = 0, len = items.length; i < len; i++) {
111345                 items[i].show();
111346             }
111347         }
111348
111349         // Resize the owning group to accommodate
111350         if (me.ownerCt.isGroupHeader) {
111351             items = me.ownerCt.query('>:not([hidden])');
111352             for (i = 0, len = items.length; i < len; i++) {
111353                 newWidth += items[i].getWidth();
111354             }
111355             me.ownerCt.minWidth = newWidth;
111356             me.ownerCt.setWidth(newWidth);
111357         }
111358
111359         // Notify owning HeaderContainer
111360         if (ownerCt) {
111361             ownerCt.onHeaderShow(me);
111362         }
111363     },
111364
111365     getDesiredWidth: function() {
111366         var me = this;
111367         if (me.rendered && me.componentLayout && me.componentLayout.lastComponentSize) {
111368             // headers always have either a width or a flex
111369             // because HeaderContainer sets a defaults width
111370             // therefore we can ignore the natural width
111371             // we use the componentLayout's tracked width so that
111372             // we can calculate the desired width when rendered
111373             // but not visible because its being obscured by a layout
111374             return me.componentLayout.lastComponentSize.width;
111375         // Flexed but yet to be rendered this could be the case
111376         // where a HeaderContainer and Headers are simply used as data
111377         // structures and not rendered.
111378         }
111379         else if (me.flex) {
111380             // this is going to be wrong, the defaultWidth
111381             return me.width;
111382         }
111383         else {
111384             return me.width;
111385         }
111386     },
111387
111388     getCellSelector: function() {
111389         return '.' + Ext.baseCSSPrefix + 'grid-cell-' + this.getItemId();
111390     },
111391
111392     getCellInnerSelector: function() {
111393         return this.getCellSelector() + ' .' + Ext.baseCSSPrefix + 'grid-cell-inner';
111394     },
111395
111396     isOnLeftEdge: function(e) {
111397         return (e.getXY()[0] - this.el.getLeft() <= this.handleWidth);
111398     },
111399
111400     isOnRightEdge: function(e) {
111401         return (this.el.getRight() - e.getXY()[0] <= this.handleWidth);
111402     }
111403     
111404     /**
111405      * Retrieves the editing field for editing associated with this header. Returns false if there
111406      * is no field associated with the Header the method will return false. If the
111407      * field has not been instantiated it will be created. Note: These methods only has an implementation
111408      * if a Editing plugin has been enabled on the grid.
111409      * @param record The {@link Ext.data.Model Model} instance being edited.
111410      * @param {Mixed} defaultField An object representing a default field to be created
111411      * @returns {Ext.form.field.Field} field
111412      * @method getEditor
111413      */
111414     // intentionally omit getEditor and setEditor definitions bc we applyIf into columns
111415     // when the editing plugin is injected
111416     
111417     
111418     /**
111419      * Sets the form field to be used for editing. Note: This method only has an implementation
111420      * if an Editing plugin has been enabled on the grid.
111421      * @param {Mixed} field An object representing a field to be created. If no xtype is specified a 'textfield' is assumed.
111422      * @method setEditor
111423      */
111424 });
111425 /**
111426  * @class Ext.grid.RowNumberer
111427  * @extends Ext.grid.column.Column
111428  * This is a utility class that can be passed into a {@link Ext.grid.column.Column} as a column config that provides
111429  * an automatic row numbering column.
111430  * <br>Usage:<br><pre><code>
111431 columns: [
111432     Ext.create('Ext.grid.RowNumberer'),
111433     {text: "Company", flex: 1, sortable: true, dataIndex: 'company'},
111434     {text: "Price", width: 120, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
111435     {text: "Change", width: 120, sortable: true, dataIndex: 'change'},
111436     {text: "% Change", width: 120, sortable: true, dataIndex: 'pctChange'},
111437     {text: "Last Updated", width: 120, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
111438 ]
111439  *</code></pre>
111440  * @constructor
111441  * @param {Object} config The configuration options
111442  */
111443 Ext.define('Ext.grid.RowNumberer', {
111444     extend: 'Ext.grid.column.Column',
111445     alias: 'widget.rownumberer',
111446     /**
111447      * @cfg {String} text Any valid text or HTML fragment to display in the header cell for the row
111448      * number column (defaults to '&#160').
111449      */
111450     text: "&#160",
111451
111452     /**
111453      * @cfg {Number} width The default width in pixels of the row number column (defaults to 23).
111454      */
111455     width: 23,
111456
111457     /**
111458      * @cfg {Boolean} sortable True if the row number column is sortable (defaults to false).
111459      * @hide
111460      */
111461     sortable: false,
111462
111463     align: 'right',
111464
111465     constructor : function(config){
111466         this.callParent(arguments);
111467         if (this.rowspan) {
111468             this.renderer = Ext.Function.bind(this.renderer, this);
111469         }
111470     },
111471
111472     // private
111473     fixed: true,
111474     hideable: false,
111475     menuDisabled: true,
111476     dataIndex: '',
111477     cls: Ext.baseCSSPrefix + 'row-numberer',
111478     rowspan: undefined,
111479
111480     // private
111481     renderer: function(value, metaData, record, rowIdx, colIdx, store) {
111482         if (this.rowspan){
111483             metaData.cellAttr = 'rowspan="'+this.rowspan+'"';
111484         }
111485
111486         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
111487         return store.indexOfTotal(record) + 1;
111488     }
111489 });
111490
111491 /**
111492  * @class Ext.view.DropZone
111493  * @extends Ext.dd.DropZone
111494  * @private
111495  */
111496 Ext.define('Ext.view.DropZone', {
111497     extend: 'Ext.dd.DropZone',
111498
111499     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
111500     indicatorCls: 'x-grid-drop-indicator',
111501
111502     constructor: function(config) {
111503         var me = this;
111504         Ext.apply(me, config);
111505
111506         // Create a ddGroup unless one has been configured.
111507         // User configuration of ddGroups allows users to specify which
111508         // DD instances can interact with each other. Using one
111509         // based on the id of the View would isolate it and mean it can only
111510         // interact with a DragZone on the same View also using a generated ID.
111511         if (!me.ddGroup) {
111512             me.ddGroup = 'view-dd-zone-' + me.view.id;
111513         }
111514
111515         // The DropZone's encapsulating element is the View's main element. It must be this because drop gestures
111516         // may require scrolling on hover near a scrolling boundary. In Ext 4.x two DD instances may not use the
111517         // same element, so a DragZone on this same View must use the View's parent element as its element.
111518         me.callParent([me.view.el]);
111519     },
111520
111521 //  Fire an event through the client DataView. Lock this DropZone during the event processing so that
111522 //  its data does not become corrupted by processing mouse events.
111523     fireViewEvent: function() {
111524         this.lock();
111525         var result = this.view.fireEvent.apply(this.view, arguments);
111526         this.unlock();
111527         return result;
111528     },
111529
111530     getTargetFromEvent : function(e) {
111531         var node = e.getTarget(this.view.getItemSelector()),
111532             mouseY, nodeList, testNode, i, len, box;
111533
111534 //      Not over a row node: The content may be narrower than the View's encapsulating element, so return the closest.
111535 //      If we fall through because the mouse is below the nodes (or there are no nodes), we'll get an onContainerOver call.
111536         if (!node) {
111537             mouseY = e.getPageY();
111538             for (i = 0, nodeList = this.view.getNodes(), len = nodeList.length; i < len; i++) {
111539                 testNode = nodeList[i];
111540                 box = Ext.fly(testNode).getBox();
111541                 if (mouseY <= box.bottom) {
111542                     return testNode;
111543                 }
111544             }
111545         }
111546         return node;
111547     },
111548
111549     getIndicator: function() {
111550         var me = this;
111551
111552         if (!me.indicator) {
111553             me.indicator = Ext.createWidget('component', {
111554                 html: me.indicatorHtml,
111555                 cls: me.indicatorCls,
111556                 ownerCt: me.view,
111557                 floating: true,
111558                 shadow: false
111559             });
111560         }
111561         return me.indicator;
111562     },
111563
111564     getPosition: function(e, node) {
111565         var y      = e.getXY()[1],
111566             region = Ext.fly(node).getRegion(),
111567             pos;
111568
111569         if ((region.bottom - y) >= (region.bottom - region.top) / 2) {
111570             pos = "before";
111571         } else {
111572             pos = "after";
111573         }
111574         return pos;
111575     },
111576
111577     /**
111578      * @private Determines whether the record at the specified offset from the passed record
111579      * is in the drag payload.
111580      * @param records
111581      * @param record
111582      * @param offset
111583      * @returns {Boolean} True if the targeted record is in the drag payload
111584      */
111585     containsRecordAtOffset: function(records, record, offset) {
111586         if (!record) {
111587             return false;
111588         }
111589         var view = this.view,
111590             recordIndex = view.indexOf(record),
111591             nodeBefore = view.getNode(recordIndex + offset),
111592             recordBefore = nodeBefore ? view.getRecord(nodeBefore) : null;
111593
111594         return recordBefore && Ext.Array.contains(records, recordBefore);
111595     },
111596
111597     positionIndicator: function(node, data, e) {
111598         var me = this,
111599             view = me.view,
111600             pos = me.getPosition(e, node),
111601             overRecord = view.getRecord(node),
111602             draggingRecords = data.records,
111603             indicator, indicatorY;
111604
111605         if (!Ext.Array.contains(draggingRecords, overRecord) && (
111606             pos == 'before' && !me.containsRecordAtOffset(draggingRecords, overRecord, -1) ||
111607             pos == 'after' && !me.containsRecordAtOffset(draggingRecords, overRecord, 1)
111608         )) {
111609             me.valid = true;
111610
111611             if (me.overRecord != overRecord || me.currentPosition != pos) {
111612
111613                 indicatorY = Ext.fly(node).getY() - view.el.getY() - 1;
111614                 if (pos == 'after') {
111615                     indicatorY += Ext.fly(node).getHeight();
111616                 }
111617                 me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, indicatorY);
111618
111619                 // Cache the overRecord and the 'before' or 'after' indicator.
111620                 me.overRecord = overRecord;
111621                 me.currentPosition = pos;
111622             }
111623         } else {
111624             me.invalidateDrop();
111625         }
111626     },
111627
111628     invalidateDrop: function() {
111629         if (this.valid) {
111630             this.valid = false;
111631             this.getIndicator().hide();
111632         }
111633     },
111634
111635     // The mouse is over a View node
111636     onNodeOver: function(node, dragZone, e, data) {
111637         if (!Ext.Array.contains(data.records, this.view.getRecord(node))) {
111638             this.positionIndicator(node, data, e);
111639         }
111640         return this.valid ? this.dropAllowed : this.dropNotAllowed;
111641     },
111642
111643     // Moved out of the DropZone without dropping.
111644     // Remove drop position indicator
111645     notifyOut: function(node, dragZone, e, data) {
111646         this.callParent(arguments);
111647         delete this.overRecord;
111648         delete this.currentPosition;
111649         if (this.indicator) {
111650             this.indicator.hide();
111651         }
111652     },
111653
111654     // The mouse is past the end of all nodes (or there are no nodes)
111655     onContainerOver : function(dd, e, data) {
111656         var v = this.view,
111657             c = v.store.getCount();
111658
111659         // There are records, so position after the last one
111660         if (c) {
111661             this.positionIndicator(v.getNode(c - 1), data, e);
111662         }
111663
111664         // No records, position the indicator at the top
111665         else {
111666             delete this.overRecord;
111667             delete this.currentPosition;
111668             this.getIndicator().setWidth(Ext.fly(v.el).getWidth()).showAt(0, 0);
111669             this.valid = true;
111670         }
111671         return this.dropAllowed;
111672     },
111673
111674     onContainerDrop : function(dd, e, data) {
111675         return this.onNodeDrop(dd, null, e, data);
111676     },
111677
111678     onNodeDrop: function(node, dragZone, e, data) {
111679         var me = this,
111680             dropped = false,
111681
111682             // Create a closure to perform the operation which the event handler may use.
111683             // Users may now return <code>0</code> from the beforedrop handler, and perform any kind
111684             // of asynchronous processing such as an Ext.Msg.confirm, or an Ajax request,
111685             // and complete the drop gesture at some point in the future by calling this function.
111686             processDrop = function () {
111687                 me.invalidateDrop();
111688                 me.handleNodeDrop(data, me.overRecord, me.currentPosition);
111689                 dropped = true;
111690                 me.fireViewEvent('drop', node, data, me.overRecord, me.currentPosition);
111691             },
111692             performOperation;
111693
111694         if (me.valid) {
111695             performOperation = me.fireViewEvent('beforedrop', node, data, me.overRecord, me.currentPosition, processDrop);
111696             if (performOperation === 0) {
111697                 return;
111698             } else if (performOperation !== false) {
111699                 // If the processDrop function was called in the event handler, do not do it again.
111700                 if (!dropped) {
111701                     processDrop();
111702                 }
111703             } else {
111704                 return false;
111705             }
111706         } else {
111707             return false;
111708         }
111709     }
111710 });
111711
111712 Ext.define('Ext.grid.ViewDropZone', {
111713     extend: 'Ext.view.DropZone',
111714
111715     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
111716     indicatorCls: 'x-grid-drop-indicator',
111717
111718     handleNodeDrop : function(data, record, position) {
111719         var view = this.view,
111720             store = view.getStore(),
111721             index, records, i, len;
111722
111723         // If the copy flag is set, create a copy of the Models with the same IDs
111724         if (data.copy) {
111725             records = data.records;
111726             data.records = [];
111727             for (i = 0, len = records.length; i < len; i++) {
111728                 data.records.push(records[i].copy(records[i].getId()));
111729             }
111730         } else {
111731             /*
111732              * Remove from the source store. We do this regardless of whether the store
111733              * is the same bacsue the store currently doesn't handle moving records
111734              * within the store. In the future it should be possible to do this.
111735              * Here was pass the isMove parameter if we're moving to the same view.
111736              */
111737             data.view.store.remove(data.records, data.view === view);
111738         }
111739
111740         index = store.indexOf(record);
111741         if (position == 'after') {
111742             index++;
111743         }
111744         store.insert(index, data.records);
111745         view.getSelectionModel().select(data.records);
111746     }
111747 });
111748 /**
111749  * @class Ext.grid.column.Action
111750  * @extends Ext.grid.column.Column
111751  * <p>A Grid header type which renders an icon, or a series of icons in a grid cell, and offers a scoped click
111752  * handler for each icon.</p>
111753  *
111754  * {@img Ext.grid.column.Action/Ext.grid.column.Action.png Ext.grid.column.Action grid column}
111755  *  
111756  * ## Code
111757  *     Ext.create('Ext.data.Store', {
111758  *         storeId:'employeeStore',
111759  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
111760  *         data:[
111761  *             {firstname:"Michael", lastname:"Scott"},
111762  *             {firstname:"Dwight", lastname:"Schrute"},
111763  *             {firstname:"Jim", lastname:"Halpert"},
111764  *             {firstname:"Kevin", lastname:"Malone"},
111765  *             {firstname:"Angela", lastname:"Martin"}                        
111766  *         ]
111767  *     });
111768  *     
111769  *     Ext.create('Ext.grid.Panel', {
111770  *         title: 'Action Column Demo',
111771  *         store: Ext.data.StoreManager.lookup('employeeStore'),
111772  *         columns: [
111773  *             {text: 'First Name',  dataIndex:'firstname'},
111774  *             {text: 'Last Name',  dataIndex:'lastname'},
111775  *             {
111776  *                 xtype:'actioncolumn', 
111777  *                 width:50,
111778  *                 items: [{
111779  *                     icon: 'images/edit.png',  // Use a URL in the icon config
111780  *                     tooltip: 'Edit',
111781  *                     handler: function(grid, rowIndex, colIndex) {
111782  *                         var rec = grid.getStore().getAt(rowIndex);
111783  *                         alert("Edit " + rec.get('firstname'));
111784  *                     }
111785  *                 },{
111786  *                     icon: 'images/delete.png',
111787  *                     tooltip: 'Delete',
111788  *                     handler: function(grid, rowIndex, colIndex) {
111789  *                         var rec = grid.getStore().getAt(rowIndex);
111790  *                         alert("Terminate " + rec.get('firstname'));
111791  *                     }                
111792  *                 }]
111793  *             }
111794  *         ],
111795  *         width: 250,
111796  *         renderTo: Ext.getBody()
111797  *     });
111798  * <p>The action column can be at any index in the columns array, and a grid can have any number of
111799  * action columns. </p>
111800  * @xtype actioncolumn
111801  */
111802 Ext.define('Ext.grid.column.Action', {
111803     extend: 'Ext.grid.column.Column',
111804     alias: ['widget.actioncolumn'],
111805     alternateClassName: 'Ext.grid.ActionColumn',
111806
111807     /**
111808      * @cfg {String} icon
111809      * The URL of an image to display as the clickable element in the column. 
111810      * Optional - defaults to <code>{@link Ext#BLANK_IMAGE_URL Ext.BLANK_IMAGE_URL}</code>.
111811      */
111812     /**
111813      * @cfg {String} iconCls
111814      * A CSS class to apply to the icon image. To determine the class dynamically, configure the Column with a <code>{@link #getClass}</code> function.
111815      */
111816     /**
111817      * @cfg {Function} handler A function called when the icon is clicked.
111818      * The handler is passed the following parameters:<div class="mdetail-params"><ul>
111819      * <li><code>view</code> : TableView<div class="sub-desc">The owning TableView.</div></li>
111820      * <li><code>rowIndex</code> : Number<div class="sub-desc">The row index clicked on.</div></li>
111821      * <li><code>colIndex</code> : Number<div class="sub-desc">The column index clicked on.</div></li>
111822      * <li><code>item</code> : Object<div class="sub-desc">The clicked item (or this Column if multiple 
111823      * {@link #items} were not configured).</div></li>
111824      * <li><code>e</code> : Event<div class="sub-desc">The click event.</div></li>
111825      * </ul></div>
111826      */
111827     /**
111828      * @cfg {Object} scope The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>
111829      * and <code>{@link #getClass}</code> fuctions are executed. Defaults to this Column.
111830      */
111831     /**
111832      * @cfg {String} tooltip A tooltip message to be displayed on hover. {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must have 
111833      * been initialized.
111834      */
111835     /**
111836      * @cfg {Boolean} stopSelection Defaults to <code>true</code>. Prevent grid <i>row</i> selection upon mousedown.
111837      */
111838     /**
111839      * @cfg {Function} getClass A function which returns the CSS class to apply to the icon image.
111840      * The function is passed the following parameters:<ul>
111841      *     <li><b>v</b> : Object<p class="sub-desc">The value of the column's configured field (if any).</p></li>
111842      *     <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
111843      *         <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
111844      *         <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container element <i>within</i> the table cell
111845      *         (e.g. 'style="color:red;"').</p></li>
111846      *     </ul></p></li>
111847      *     <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data.</p></li>
111848      *     <li><b>rowIndex</b> : Number<p class="sub-desc">The row index..</p></li>
111849      *     <li><b>colIndex</b> : Number<p class="sub-desc">The column index.</p></li>
111850      *     <li><b>store</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
111851      * </ul>
111852      */
111853     /**
111854      * @cfg {Array} items An Array which may contain multiple icon definitions, each element of which may contain:
111855      * <div class="mdetail-params"><ul>
111856      * <li><code>icon</code> : String<div class="sub-desc">The url of an image to display as the clickable element 
111857      * in the column.</div></li>
111858      * <li><code>iconCls</code> : String<div class="sub-desc">A CSS class to apply to the icon image.
111859      * To determine the class dynamically, configure the item with a <code>getClass</code> function.</div></li>
111860      * <li><code>getClass</code> : Function<div class="sub-desc">A function which returns the CSS class to apply to the icon image.
111861      * The function is passed the following parameters:<ul>
111862      *     <li><b>v</b> : Object<p class="sub-desc">The value of the column's configured field (if any).</p></li>
111863      *     <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
111864      *         <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
111865      *         <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container element <i>within</i> the table cell
111866      *         (e.g. 'style="color:red;"').</p></li>
111867      *     </ul></p></li>
111868      *     <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data.</p></li>
111869      *     <li><b>rowIndex</b> : Number<p class="sub-desc">The row index..</p></li>
111870      *     <li><b>colIndex</b> : Number<p class="sub-desc">The column index.</p></li>
111871      *     <li><b>store</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
111872      * </ul></div></li>
111873      * <li><code>handler</code> : Function<div class="sub-desc">A function called when the icon is clicked.</div></li>
111874      * <li><code>scope</code> : Scope<div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the 
111875      * <code>handler</code> and <code>getClass</code> functions are executed. Fallback defaults are this Column's
111876      * configured scope, then this Column.</div></li>
111877      * <li><code>tooltip</code> : String<div class="sub-desc">A tooltip message to be displayed on hover. 
111878      * {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must have been initialized.</div></li>
111879      * </ul></div>
111880      */
111881     header: '&#160;',
111882
111883     actionIdRe: /x-action-col-(\d+)/,
111884
111885     /**
111886      * @cfg {String} altText The alt text to use for the image element. Defaults to <tt>''</tt>.
111887      */
111888     altText: '',
111889     
111890     sortable: false,
111891
111892     constructor: function(config) {
111893         var me = this,
111894             cfg = Ext.apply({}, config),
111895             items = cfg.items || [me],
111896             l = items.length,
111897             i,
111898             item;
111899
111900         // This is a Container. Delete the items config to be reinstated after construction.
111901         delete cfg.items;
111902         me.callParent([cfg]);
111903
111904         // Items is an array property of ActionColumns
111905         me.items = items;
111906
111907 //      Renderer closure iterates through items creating an <img> element for each and tagging with an identifying 
111908 //      class name x-action-col-{n}
111909         me.renderer = function(v, meta) {
111910 //          Allow a configured renderer to create initial value (And set the other values in the "metadata" argument!)
111911             v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '';
111912
111913             meta.tdCls += ' ' + Ext.baseCSSPrefix + 'action-col-cell';
111914             for (i = 0; i < l; i++) {
111915                 item = items[i];
111916                 v += '<img alt="' + me.altText + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
111917                     '" class="' + Ext.baseCSSPrefix + 'action-col-icon ' + Ext.baseCSSPrefix + 'action-col-' + String(i) + ' ' +  (item.iconCls || '') + 
111918                     ' ' + (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope||me.scope||me, arguments) : (me.iconCls || '')) + '"' +
111919                     ((item.tooltip) ? ' data-qtip="' + item.tooltip + '"' : '') + ' />';
111920             }
111921             return v;
111922         };
111923     },
111924
111925     destroy: function() {
111926         delete this.items;
111927         delete this.renderer;
111928         return this.callParent(arguments);
111929     },
111930
111931     /**
111932      * @private
111933      * Process and refire events routed from the GridView's processEvent method.
111934      * Also fires any configured click handlers. By default, cancels the mousedown event to prevent selection.
111935      * Returns the event handler's status to allow canceling of GridView's bubbling process.
111936      */
111937     processEvent : function(type, view, cell, recordIndex, cellIndex, e){
111938         var me = this,
111939             match = e.getTarget().className.match(me.actionIdRe),
111940             item, fn;
111941         if (match) {
111942             item = me.items[parseInt(match[1], 10)];
111943             if (item) {
111944                 if (type == 'click') {
111945                     fn = item.handler || me.handler;
111946                     if (fn) {
111947                         fn.call(item.scope || me.scope || me, view, recordIndex, cellIndex, item, e);
111948                     }
111949                 } else if (type == 'mousedown' && item.stopSelection !== false) {
111950                     return false;
111951                 }
111952             }
111953         }
111954         return me.callParent(arguments);
111955     },
111956
111957     cascade: function(fn, scope) {
111958         fn.call(scope||this, this);
111959     },
111960
111961     // Private override because this cannot function as a Container, and it has an items property which is an Array, NOT a MixedCollection.
111962     getRefItems: function() {
111963         return [];
111964     }
111965 });
111966 /**
111967  * @class Ext.grid.column.Boolean
111968  * @extends Ext.grid.column.Column
111969  * <p>A Column definition class which renders boolean data fields.  See the {@link Ext.grid.column.Column#xtype xtype}
111970  * config option of {@link Ext.grid.column.Column} for more details.</p>
111971  *
111972  * {@img Ext.grid.column.Boolean/Ext.grid.column.Boolean.png Ext.grid.column.Boolean grid column}
111973  *
111974  * ## Code
111975  *     Ext.create('Ext.data.Store', {
111976  *        storeId:'sampleStore',
111977  *        fields:[
111978  *            {name: 'framework', type: 'string'},
111979  *            {name: 'rocks', type: 'boolean'}
111980  *        ],
111981  *        data:{'items':[
111982  *            {"framework":"Ext JS 4", "rocks":true},
111983  *            {"framework":"Sencha Touch", "rocks":true},
111984  *            {"framework":"Ext GWT", "rocks":true},            
111985  *            {"framework":"Other Guys", "rocks":false}            
111986  *        ]},
111987  *        proxy: {
111988  *            type: 'memory',
111989  *            reader: {
111990  *                type: 'json',
111991  *                root: 'items'
111992  *            }
111993  *        }
111994  *     });
111995  *     
111996  *     Ext.create('Ext.grid.Panel', {
111997  *         title: 'Boolean Column Demo',
111998  *         store: Ext.data.StoreManager.lookup('sampleStore'),
111999  *         columns: [
112000  *             {text: 'Framework',  dataIndex: 'framework', flex: 1},
112001  *             {
112002  *                 xtype: 'booleancolumn', 
112003  *                 text: 'Rocks',
112004  *                 trueText: 'Yes',
112005  *                 falseText: 'No', 
112006  *                 dataIndex: 'rocks'}
112007  *         ],
112008  *         height: 200,
112009  *         width: 400,
112010  *         renderTo: Ext.getBody()
112011  *     });
112012  * 
112013  * @xtype booleancolumn
112014  */
112015 Ext.define('Ext.grid.column.Boolean', {
112016     extend: 'Ext.grid.column.Column',
112017     alias: ['widget.booleancolumn'],
112018     alternateClassName: 'Ext.grid.BooleanColumn',
112019
112020     /**
112021      * @cfg {String} trueText
112022      * The string returned by the renderer when the column value is not falsey (defaults to <tt>'true'</tt>).
112023      */
112024     trueText: 'true',
112025
112026     /**
112027      * @cfg {String} falseText
112028      * The string returned by the renderer when the column value is falsey (but not undefined) (defaults to
112029      * <tt>'false'</tt>).
112030      */
112031     falseText: 'false',
112032
112033     /**
112034      * @cfg {String} undefinedText
112035      * The string returned by the renderer when the column value is undefined (defaults to <tt>'&#160;'</tt>).
112036      */
112037     undefinedText: '&#160;',
112038
112039     constructor: function(cfg){
112040         this.callParent(arguments);
112041         var trueText      = this.trueText,
112042             falseText     = this.falseText,
112043             undefinedText = this.undefinedText;
112044
112045         this.renderer = function(value){
112046             if(value === undefined){
112047                 return undefinedText;
112048             }
112049             if(!value || value === 'false'){
112050                 return falseText;
112051             }
112052             return trueText;
112053         };
112054     }
112055 });
112056 /**
112057  * @class Ext.grid.column.Date
112058  * @extends Ext.grid.column.Column
112059  *
112060  * A Column definition class which renders a passed date according to the default locale, or a configured
112061  * {@link #format}.
112062  *
112063  * {@img Ext.grid.column.Date/Ext.grid.column.Date.png Ext.grid.column.Date grid column}
112064  *
112065  * ## Code
112066  *
112067  *     Ext.create('Ext.data.Store', {
112068  *         storeId:'sampleStore',
112069  *         fields:[
112070  *             {name: 'symbol', type: 'string'},
112071  *             {name: 'date', type: 'date'},
112072  *             {name: 'change', type: 'number'},
112073  *             {name: 'volume', type: 'number'},
112074  *             {name: 'topday', type: 'date'}                        
112075  *         ],
112076  *         data:[
112077  *             {symbol:"msft", date:'2011/04/22', change:2.43, volume:61606325, topday:'04/01/2010'},
112078  *             {symbol:"goog", date:'2011/04/22', change:0.81, volume:3053782, topday:'04/11/2010'},
112079  *             {symbol:"apple", date:'2011/04/22', change:1.35, volume:24484858, topday:'04/28/2010'},            
112080  *             {symbol:"sencha", date:'2011/04/22', change:8.85, volume:5556351, topday:'04/22/2010'}            
112081  *         ]
112082  *     });
112083  *     
112084  *     Ext.create('Ext.grid.Panel', {
112085  *         title: 'Date Column Demo',
112086  *         store: Ext.data.StoreManager.lookup('sampleStore'),
112087  *         columns: [
112088  *             {text: 'Symbol',  dataIndex: 'symbol', flex: 1},
112089  *             {text: 'Date',  dataIndex: 'date', xtype: 'datecolumn', format:'Y-m-d'},
112090  *             {text: 'Change',  dataIndex: 'change', xtype: 'numbercolumn', format:'0.00'},
112091  *             {text: 'Volume',  dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000'},
112092  *             {text: 'Top Day',  dataIndex: 'topday', xtype: 'datecolumn', format:'l'}            
112093  *         ],
112094  *         height: 200,
112095  *         width: 450,
112096  *         renderTo: Ext.getBody()
112097  *     });
112098  *    
112099  * @xtype datecolumn
112100  */
112101 Ext.define('Ext.grid.column.Date', {
112102     extend: 'Ext.grid.column.Column',
112103     alias: ['widget.datecolumn'],
112104     requires: ['Ext.Date'],
112105     alternateClassName: 'Ext.grid.DateColumn',
112106
112107     /**
112108      * @cfg {String} format
112109      * A formatting string as used by {@link Date#format Date.format} to format a Date for this Column.
112110      * This defaults to the default date from {@link Ext.Date#defaultFormat} which itself my be overridden
112111      * in a locale file.
112112      */
112113     format : Ext.Date.defaultFormat,
112114
112115     constructor: function(cfg){
112116         this.callParent(arguments);
112117         this.renderer = Ext.util.Format.dateRenderer(this.format);
112118     }
112119 });
112120 /**
112121  * @class Ext.grid.column.Number
112122  * @extends Ext.grid.column.Column
112123  *
112124  * A Column definition class which renders a numeric data field according to a {@link #format} string.
112125  *
112126  * {@img Ext.grid.column.Number/Ext.grid.column.Number.png Ext.grid.column.Number cell editing}
112127  *
112128  * ## Code
112129  *     Ext.create('Ext.data.Store', {
112130  *        storeId:'sampleStore',
112131  *        fields:[
112132  *            {name: 'symbol', type: 'string'},
112133  *            {name: 'price', type: 'number'},
112134  *            {name: 'change', type: 'number'},
112135  *            {name: 'volume', type: 'number'},            
112136  *        ],
112137  *        data:[
112138  *            {symbol:"msft", price:25.76, change:2.43, volume:61606325},
112139  *            {symbol:"goog", price:525.73, change:0.81, volume:3053782},
112140  *            {symbol:"apple", price:342.41, change:1.35, volume:24484858},            
112141  *            {symbol:"sencha", price:142.08, change:8.85, volume:5556351}            
112142  *        ]
112143  *     });
112144  *     
112145  *     Ext.create('Ext.grid.Panel', {
112146  *         title: 'Number Column Demo',
112147  *         store: Ext.data.StoreManager.lookup('sampleStore'),
112148  *         columns: [
112149  *             {text: 'Symbol',  dataIndex: 'symbol', flex: 1},
112150  *             {text: 'Current Price',  dataIndex: 'price', renderer: Ext.util.Format.usMoney},
112151  *             {text: 'Change',  dataIndex: 'change', xtype: 'numbercolumn', format:'0.00'},
112152  *             {text: 'Volume',  dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000'}
112153  *         ],
112154  *         height: 200,
112155  *         width: 400,
112156  *         renderTo: Ext.getBody()
112157  *     });
112158  * 
112159  * @xtype numbercolumn
112160  */
112161 Ext.define('Ext.grid.column.Number', {
112162     extend: 'Ext.grid.column.Column',
112163     alias: ['widget.numbercolumn'],
112164     requires: ['Ext.util.Format'],
112165     alternateClassName: 'Ext.grid.NumberColumn',
112166
112167     /**
112168      * @cfg {String} format
112169      * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column
112170      * (defaults to <code>'0,000.00'</code>).
112171      */
112172     format : '0,000.00',
112173     constructor: function(cfg) {
112174         this.callParent(arguments);
112175         this.renderer = Ext.util.Format.numberRenderer(this.format);
112176     }
112177 });
112178 /**
112179  * @class Ext.grid.column.Template
112180  * @extends Ext.grid.column.Column
112181  * 
112182  * A Column definition class which renders a value by processing a {@link Ext.data.Model Model}'s
112183  * {@link Ext.data.Model#data data} using a {@link #tpl configured} {@link Ext.XTemplate XTemplate}.
112184  * 
112185  *  {@img Ext.grid.column.Template/Ext.grid.column.Template.png Ext.grid.column.Template grid column}
112186  * 
112187  * ## Code
112188  *     Ext.create('Ext.data.Store', {
112189  *         storeId:'employeeStore',
112190  *         fields:['firstname', 'lastname', 'senority', 'department'],
112191  *         groupField: 'department',
112192  *         data:[
112193  *             {firstname:"Michael", lastname:"Scott", senority:7, department:"Manangement"},
112194  *             {firstname:"Dwight", lastname:"Schrute", senority:2, department:"Sales"},
112195  *             {firstname:"Jim", lastname:"Halpert", senority:3, department:"Sales"},
112196  *             {firstname:"Kevin", lastname:"Malone", senority:4, department:"Accounting"},
112197  *             {firstname:"Angela", lastname:"Martin", senority:5, department:"Accounting"}                        
112198  *         ]
112199  *     });
112200  *     
112201  *     Ext.create('Ext.grid.Panel', {
112202  *         title: 'Column Template Demo',
112203  *         store: Ext.data.StoreManager.lookup('employeeStore'),
112204  *         columns: [
112205  *             {text: 'Full Name',  xtype:'templatecolumn', tpl:'{firstname} {lastname}', flex:1},
112206  *             {text: 'Deparment (Yrs)', xtype:'templatecolumn', tpl:'{department} ({senority})'}
112207  *         ],
112208  *         height: 200,
112209  *         width: 300,
112210  *         renderTo: Ext.getBody()
112211  *     });
112212  * 
112213  * @markdown
112214  * @xtype templatecolumn
112215  */
112216 Ext.define('Ext.grid.column.Template', {
112217     extend: 'Ext.grid.column.Column',
112218     alias: ['widget.templatecolumn'],
112219     requires: ['Ext.XTemplate'],
112220     alternateClassName: 'Ext.grid.TemplateColumn',
112221
112222     /**
112223      * @cfg {String/XTemplate} tpl
112224      * An {@link Ext.XTemplate XTemplate}, or an XTemplate <i>definition string</i> to use to process a
112225      * {@link Ext.data.Model Model}'s {@link Ext.data.Model#data data} to produce a column's rendered value.
112226      */
112227     constructor: function(cfg){
112228         var me = this,
112229             tpl;
112230             
112231         me.callParent(arguments);
112232         tpl = me.tpl = (!Ext.isPrimitive(me.tpl) && me.tpl.compile) ? me.tpl : Ext.create('Ext.XTemplate', me.tpl);
112233
112234         me.renderer = function(value, p, record) {
112235             var data = Ext.apply({}, record.data, record.getAssociatedData());
112236             return tpl.apply(data);
112237         };
112238     }
112239 });
112240
112241 /**
112242  * @class Ext.grid.feature.Feature
112243  * @extends Ext.util.Observable
112244  * 
112245  * A feature is a type of plugin that is specific to the {@link Ext.grid.Panel}. It provides several
112246  * hooks that allows the developer to inject additional functionality at certain points throughout the 
112247  * grid creation cycle. This class provides the base template methods that are available to the developer,
112248  * it should be extended.
112249  * 
112250  * There are several built in features that extend this class, for example:
112251  *
112252  *  - {@link Ext.grid.feature.Grouping} - Shows grid rows in groups as specified by the {@link Ext.data.Store}
112253  *  - {@link Ext.grid.feature.RowBody} - Adds a body section for each grid row that can contain markup.
112254  *  - {@link Ext.grid.feature.Summary} - Adds a summary row at the bottom of the grid with aggregate totals for a column.
112255  * 
112256  * ## Using Features
112257  * A feature is added to the grid by specifying it an array of features in the configuration:
112258  * 
112259  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping');
112260  *     Ext.create('Ext.grid.Panel', {
112261  *         // other options
112262  *         features: [groupingFeature]
112263  *     });
112264  * 
112265  * @abstract
112266  */
112267 Ext.define('Ext.grid.feature.Feature', {
112268     extend: 'Ext.util.Observable',
112269     alias: 'feature.feature',
112270     
112271     isFeature: true,
112272     disabled: false,
112273     
112274     /**
112275      * @property {Boolean}
112276      * Most features will expose additional events, some may not and will
112277      * need to change this to false.
112278      */
112279     hasFeatureEvent: true,
112280     
112281     /**
112282      * @property {String}
112283      * Prefix to use when firing events on the view.
112284      * For example a prefix of group would expose "groupclick", "groupcontextmenu", "groupdblclick".
112285      */
112286     eventPrefix: null,
112287     
112288     /**
112289      * @property {String}
112290      * Selector used to determine when to fire the event with the eventPrefix.
112291      */
112292     eventSelector: null,
112293     
112294     /**
112295      * @property {Ext.view.Table}
112296      * Reference to the TableView.
112297      */
112298     view: null,
112299     
112300     /**
112301      * @property {Ext.grid.Panel}
112302      * Reference to the grid panel
112303      */
112304     grid: null,
112305     
112306     /**
112307      * Most features will not modify the data returned to the view.
112308      * This is limited to one feature that manipulates the data per grid view.
112309      */
112310     collectData: false,
112311         
112312     getFeatureTpl: function() {
112313         return '';
112314     },
112315     
112316     /**
112317      * Abstract method to be overriden when a feature should add additional
112318      * arguments to its event signature. By default the event will fire:
112319      * - view - The underlying Ext.view.Table
112320      * - featureTarget - The matched element by the defined {@link eventSelector}
112321      *
112322      * The method must also return the eventName as the first index of the array
112323      * to be passed to fireEvent.
112324      */
112325     getFireEventArgs: function(eventName, view, featureTarget, e) {
112326         return [eventName, view, featureTarget, e];
112327     },
112328     
112329     /**
112330      * Approriate place to attach events to the view, selectionmodel, headerCt, etc
112331      */
112332     attachEvents: function() {
112333         
112334     },
112335     
112336     getFragmentTpl: function() {
112337         return;
112338     },
112339     
112340     /**
112341      * Allows a feature to mutate the metaRowTpl.
112342      * The array received as a single argument can be manipulated to add things
112343      * on the end/begining of a particular row.
112344      */
112345     mutateMetaRowTpl: function(metaRowTplArray) {
112346         
112347     },
112348     
112349     /**
112350      * Allows a feature to inject member methods into the metaRowTpl. This is
112351      * important for embedding functionality which will become part of the proper
112352      * row tpl.
112353      */
112354     getMetaRowTplFragments: function() {
112355         return {};
112356     },
112357
112358     getTableFragments: function() {
112359         return {};
112360     },
112361     
112362     /**
112363      * Provide additional data to the prepareData call within the grid view.
112364      * @param {Object} data The data for this particular record.
112365      * @param {Number} idx The row index for this record.
112366      * @param {Ext.data.Model} record The record instance
112367      * @param {Object} orig The original result from the prepareData call to massage.
112368      */
112369     getAdditionalData: function(data, idx, record, orig) {
112370         return {};
112371     },
112372     
112373     /**
112374      * Enable a feature
112375      */
112376     enable: function() {
112377         this.disabled = false;
112378     },
112379     
112380     /**
112381      * Disable a feature
112382      */
112383     disable: function() {
112384         this.disabled = true;
112385     }
112386     
112387 });
112388 /**
112389  * A small abstract class that contains the shared behaviour for any summary
112390  * calculations to be used in the grid.
112391  * @class Ext.grid.feature.AbstractSummary
112392  * @extends Ext.grid.feature.Feature
112393  * @ignore
112394  */
112395 Ext.define('Ext.grid.feature.AbstractSummary', {
112396     
112397     /* Begin Definitions */
112398    
112399     extend: 'Ext.grid.feature.Feature',
112400     
112401     alias: 'feature.abstractsummary',
112402    
112403     /* End Definitions */
112404    
112405    /**
112406     * @cfg {Boolean} showSummaryRow True to show the summary row. Defaults to <tt>true</tt>.
112407     */
112408     showSummaryRow: true,
112409     
112410     // @private
112411     nestedIdRe: /\{\{id\}([\w\-]*)\}/g,
112412     
112413     /**
112414      * Toggle whether or not to show the summary row.
112415      * @param {Boolan} visible True to show the summary row
112416      */
112417     toggleSummaryRow: function(visible){
112418         this.showSummaryRow = !!visible;
112419     },
112420     
112421     /**
112422      * Gets any fragments to be used in the tpl
112423      * @private
112424      * @return {Object} The fragments
112425      */
112426     getSummaryFragments: function(){
112427         var fragments = {};
112428         if (this.showSummaryRow) {
112429             Ext.apply(fragments, {
112430                 printSummaryRow: Ext.bind(this.printSummaryRow, this)
112431             });
112432         }
112433         return fragments;
112434     },
112435     
112436     /**
112437      * Prints a summary row
112438      * @private
112439      * @param {Object} index The index in the template
112440      * @return {String} The value of the summary row
112441      */
112442     printSummaryRow: function(index){
112443         var inner = this.view.getTableChunker().metaRowTpl.join('');
112444         
112445         inner = inner.replace('x-grid-row', 'x-grid-row-summary');
112446         inner = inner.replace('{{id}}', '{gridSummaryValue}');
112447         inner = inner.replace(this.nestedIdRe, '{id$1}');  
112448         inner = inner.replace('{[this.embedRowCls()]}', '{rowCls}');
112449         inner = inner.replace('{[this.embedRowAttr()]}', '{rowAttr}');
112450         inner = Ext.create('Ext.XTemplate', inner, {
112451             firstOrLastCls: Ext.view.TableChunker.firstOrLastCls
112452         });
112453         
112454         return inner.applyTemplate({
112455             columns: this.getPrintData(index)
112456         });
112457     },
112458     
112459     /**
112460      * Gets the value for the column from the attached data.
112461      * @param {Ext.grid.column.Column} column The header
112462      * @param {Object} data The current data
112463      * @return {String} The value to be rendered
112464      */
112465     getColumnValue: function(column, summaryData){
112466         var comp     = Ext.getCmp(column.id),
112467             value    = summaryData[column.dataIndex],
112468             renderer = comp.summaryRenderer;
112469
112470         if (renderer) {
112471             value = renderer.call(
112472                 comp.scope || this,
112473                 value,
112474                 summaryData,
112475                 column.dataIndex
112476             );
112477         }
112478         return value;
112479     },
112480     
112481     /**
112482      * Get the summary data for a field.
112483      * @private
112484      * @param {Ext.data.Store} store The store to get the data from
112485      * @param {String/Function} type The type of aggregation. If a function is specified it will
112486      * be passed to the stores aggregate function.
112487      * @param {String} field The field to aggregate on
112488      * @param {Boolean} group True to aggregate in grouped mode 
112489      * @return {Mixed} See the return type for the store functions.
112490      */
112491     getSummary: function(store, type, field, group){
112492         if (type) {
112493             if (Ext.isFunction(type)) {
112494                 return store.aggregate(type, null, group);
112495             }
112496             
112497             switch (type) {
112498                 case 'count':
112499                     return store.count(group);
112500                 case 'min':
112501                     return store.min(field, group);
112502                 case 'max':
112503                     return store.max(field, group);
112504                 case 'sum':
112505                     return store.sum(field, group);
112506                 case 'average':
112507                     return store.average(field, group);
112508                 default:
112509                     return group ? {} : '';
112510                     
112511             }
112512         }
112513     }
112514     
112515 });
112516
112517 /**
112518  * @class Ext.grid.feature.Chunking
112519  * @extends Ext.grid.feature.Feature
112520  */
112521 Ext.define('Ext.grid.feature.Chunking', {
112522     extend: 'Ext.grid.feature.Feature',
112523     alias: 'feature.chunking',
112524     
112525     chunkSize: 20,
112526     rowHeight: Ext.isIE ? 27 : 26,
112527     visibleChunk: 0,
112528     hasFeatureEvent: false,
112529     attachEvents: function() {
112530         var grid = this.view.up('gridpanel'),
112531             scroller = grid.down('gridscroller[dock=right]');
112532         scroller.el.on('scroll', this.onBodyScroll, this, {buffer: 300});
112533         //this.view.on('bodyscroll', this.onBodyScroll, this, {buffer: 300});
112534     },
112535     
112536     onBodyScroll: function(e, t) {
112537         var view = this.view,
112538             top  = t.scrollTop,
112539             nextChunk = Math.floor(top / this.rowHeight / this.chunkSize);
112540         if (nextChunk !== this.visibleChunk) {
112541         
112542             this.visibleChunk = nextChunk;
112543             view.refresh();
112544             view.el.dom.scrollTop = top;
112545             //BrowserBug: IE6,7,8 quirks mode takes setting scrollTop 2x.
112546             view.el.dom.scrollTop = top;
112547         }
112548     },
112549     
112550     collectData: function(records, preppedRecords, startIndex, fullWidth, orig) {
112551         var o = {
112552             fullWidth: orig.fullWidth,
112553             chunks: []
112554         },
112555         //headerCt = this.view.headerCt,
112556         //colums = headerCt.getColumnsForTpl(),
112557         recordCount = orig.rows.length,
112558         start = 0,
112559         i = 0,
112560         visibleChunk = this.visibleChunk,
112561         chunk,
112562         rows,
112563         chunkLength;
112564
112565         for (; start < recordCount; start+=this.chunkSize, i++) {
112566             if (start+this.chunkSize > recordCount) {
112567                 chunkLength = recordCount - start;
112568             } else {
112569                 chunkLength = this.chunkSize;
112570             }
112571             
112572             if (i >= visibleChunk - 1 && i <= visibleChunk + 1) {
112573                 rows = orig.rows.slice(start, start+this.chunkSize);
112574             } else {
112575                 rows = [];
112576             }
112577             o.chunks.push({
112578                 rows: rows,
112579                 fullWidth: fullWidth,
112580                 chunkHeight: chunkLength * this.rowHeight
112581             });
112582         }
112583         
112584         
112585         return o;
112586     },
112587     
112588     getTableFragments: function() {
112589         return {
112590             openTableWrap: function() {
112591                 return '<tpl for="chunks"><div class="' + Ext.baseCSSPrefix + 'grid-chunk" style="height: {chunkHeight}px;">';
112592             },
112593             closeTableWrap: function() {
112594                 return '</div></tpl>';
112595             }
112596         };
112597     }
112598 });
112599
112600 /**
112601  * @class Ext.grid.feature.Grouping
112602  * @extends Ext.grid.feature.Feature
112603  * 
112604  * This feature allows to display the grid rows aggregated into groups as specified by the {@link Ext.data.Store#groupers}
112605  * specified on the Store. The group will show the title for the group name and then the appropriate records for the group
112606  * underneath. The groups can also be expanded and collapsed.
112607  * 
112608  * ## Extra Events
112609  * This feature adds several extra events that will be fired on the grid to interact with the groups:
112610  *
112611  *  - {@link #groupclick}
112612  *  - {@link #groupdblclick}
112613  *  - {@link #groupcontextmenu}
112614  *  - {@link #groupexpand}
112615  *  - {@link #groupcollapse}
112616  * 
112617  * ## Menu Augmentation
112618  * This feature adds extra options to the grid column menu to provide the user with functionality to modify the grouping.
112619  * This can be disabled by setting the {@link #enableGroupingMenu} option. The option to disallow grouping from being turned off
112620  * by thew user is {@link #enableNoGroups}.
112621  * 
112622  * ## Controlling Group Text
112623  * The {@link #groupHeaderTpl} is used to control the rendered title for each group. It can modified to customized
112624  * the default display.
112625  * 
112626  * ## Example Usage
112627  * 
112628  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
112629  *         groupHeaderTpl: 'Group: {name} ({rows.length})', //print the number of items in the group
112630  *         startCollapsed: true // start all groups collapsed
112631  *     });
112632  * 
112633  * @ftype grouping
112634  * @author Nicolas Ferrero
112635  */
112636 Ext.define('Ext.grid.feature.Grouping', {
112637     extend: 'Ext.grid.feature.Feature',
112638     alias: 'feature.grouping',
112639
112640     eventPrefix: 'group',
112641     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
112642
112643     constructor: function() {
112644         this.collapsedState = {};
112645         this.callParent(arguments);
112646     },
112647     
112648     /**
112649      * @event groupclick
112650      * @param {Ext.view.Table} view
112651      * @param {HTMLElement} node
112652      * @param {String} group The name of the group
112653      * @param {Ext.EventObject} e
112654      */
112655
112656     /**
112657      * @event groupdblclick
112658      * @param {Ext.view.Table} view
112659      * @param {HTMLElement} node
112660      * @param {String} group The name of the group
112661      * @param {Ext.EventObject} e
112662      */
112663
112664     /**
112665      * @event groupcontextmenu
112666      * @param {Ext.view.Table} view
112667      * @param {HTMLElement} node
112668      * @param {String} group The name of the group
112669      * @param {Ext.EventObject} e
112670      */
112671
112672     /**
112673      * @event groupcollapse
112674      * @param {Ext.view.Table} view
112675      * @param {HTMLElement} node
112676      * @param {String} group The name of the group
112677      * @param {Ext.EventObject} e
112678      */
112679
112680     /**
112681      * @event groupexpand
112682      * @param {Ext.view.Table} view
112683      * @param {HTMLElement} node
112684      * @param {String} group The name of the group
112685      * @param {Ext.EventObject} e
112686      */
112687
112688     /**
112689      * @cfg {String} groupHeaderTpl
112690      * Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
112691      * Defaults to 'Group: {name}'
112692      */
112693     groupHeaderTpl: 'Group: {name}',
112694
112695     /**
112696      * @cfg {Number} depthToIndent
112697      * Number of pixels to indent per grouping level
112698      */
112699     depthToIndent: 17,
112700
112701     collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
112702     hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
112703
112704     /**
112705      * @cfg {String} groupByText Text displayed in the grid header menu for grouping by header
112706      * (defaults to 'Group By This Field').
112707      */
112708     groupByText : 'Group By This Field',
112709     /**
112710      * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping
112711      * (defaults to 'Show in Groups').
112712      */
112713     showGroupsText : 'Show in Groups',
112714
112715     /**
112716      * @cfg {Boolean} hideGroupedHeader<tt>true</tt> to hide the header that is currently grouped (defaults to <tt>false</tt>)
112717      */
112718     hideGroupedHeader : false,
112719
112720     /**
112721      * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed (defaults to <tt>false</tt>)
112722      */
112723     startCollapsed : false,
112724
112725     /**
112726      * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the header menu (defaults to <tt>true</tt>)
112727      */
112728     enableGroupingMenu : true,
112729
112730     /**
112731      * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping (defaults to <tt>true</tt>)
112732      */
112733     enableNoGroups : true,
112734     
112735     enable: function() {
112736         var me    = this,
112737             view  = me.view,
112738             store = view.store,
112739             groupToggleMenuItem;
112740             
112741         if (me.lastGroupIndex) {
112742             store.group(me.lastGroupIndex);
112743         }
112744         me.callParent();
112745         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
112746         groupToggleMenuItem.setChecked(true, true);
112747         view.refresh();
112748     },
112749
112750     disable: function() {
112751         var me    = this,
112752             view  = me.view,
112753             store = view.store,
112754             groupToggleMenuItem,
112755             lastGroup;
112756             
112757         lastGroup = store.groupers.first();
112758         if (lastGroup) {
112759             me.lastGroupIndex = lastGroup.property;
112760             store.groupers.clear();
112761         }
112762         
112763         me.callParent();
112764         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
112765         groupToggleMenuItem.setChecked(true, true);
112766         groupToggleMenuItem.setChecked(false, true);
112767         view.refresh();
112768     },
112769
112770     getFeatureTpl: function(values, parent, x, xcount) {
112771         var me = this;
112772         
112773         return [
112774             '<tpl if="typeof rows !== \'undefined\'">',
112775                 // group row tpl
112776                 '<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>',
112777                 // this is the rowbody
112778                 '<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>',
112779             '</tpl>'
112780         ].join('');
112781     },
112782
112783     getFragmentTpl: function() {
112784         return {
112785             indentByDepth: this.indentByDepth,
112786             depthToIndent: this.depthToIndent
112787         };
112788     },
112789
112790     indentByDepth: function(values) {
112791         var depth = values.depth || 0;
112792         return 'style="padding-left:'+ depth * this.depthToIndent + 'px;"';
112793     },
112794
112795     // Containers holding these components are responsible for
112796     // destroying them, we are just deleting references.
112797     destroy: function() {
112798         var me = this;
112799         
112800         delete me.view;
112801         delete me.prunedHeader;
112802     },
112803
112804     // perhaps rename to afterViewRender
112805     attachEvents: function() {
112806         var me = this,
112807             view = me.view,
112808             header, headerId, menu, menuItem;
112809
112810         view.on({
112811             scope: me,
112812             groupclick: me.onGroupClick,
112813             rowfocus: me.onRowFocus
112814         });
112815         view.store.on('groupchange', me.onGroupChange, me);
112816
112817         me.pruneGroupedHeader();
112818
112819         if (me.enableGroupingMenu) {
112820             me.injectGroupingMenu();
112821         }
112822
112823         if (me.hideGroupedHeader) {
112824             header = view.headerCt.down('gridcolumn[dataIndex=' + me.getGroupField() + ']');
112825             headerId = header.id;
112826             menu = view.headerCt.getMenu();
112827             menuItem = menu.down('menuitem[headerId='+ headerId +']');
112828             if (menuItem) {
112829                 menuItem.setChecked(false);
112830             }
112831         }
112832     },
112833     
112834     injectGroupingMenu: function() {
112835         var me       = this,
112836             view     = me.view,
112837             headerCt = view.headerCt;
112838         headerCt.showMenuBy = me.showMenuBy;
112839         headerCt.getMenuItems = me.getMenuItems();
112840     },
112841     
112842     showMenuBy: function(t, header) {
112843         var menu = this.getMenu(),
112844             groupMenuItem  = menu.down('#groupMenuItem'),
112845             groupableMth = header.groupable === false ?  'disable' : 'enable';
112846             
112847         groupMenuItem[groupableMth]();
112848         Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
112849     },
112850     
112851     getMenuItems: function() {
112852         var me                 = this,
112853             groupByText        = me.groupByText,
112854             disabled           = me.disabled,
112855             showGroupsText     = me.showGroupsText,
112856             enableNoGroups     = me.enableNoGroups,
112857             groupMenuItemClick = Ext.Function.bind(me.onGroupMenuItemClick, me),
112858             groupToggleMenuItemClick = Ext.Function.bind(me.onGroupToggleMenuItemClick, me);
112859         
112860         // runs in the scope of headerCt
112861         return function() {
112862             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
112863             o.push('-', {
112864                 itemId: 'groupMenuItem',
112865                 text: groupByText,
112866                 handler: groupMenuItemClick
112867             });
112868             if (enableNoGroups) {
112869                 o.push({
112870                     itemId: 'groupToggleMenuItem',
112871                     text: showGroupsText,
112872                     checked: !disabled,
112873                     checkHandler: groupToggleMenuItemClick
112874                 });
112875             }
112876             return o;
112877         };
112878     },
112879
112880
112881     /**
112882      * Group by the header the user has clicked on.
112883      * @private
112884      */
112885     onGroupMenuItemClick: function(menuItem, e) {
112886         var menu = menuItem.parentMenu,
112887             hdr  = menu.activeHeader,
112888             view = this.view;
112889
112890         delete this.lastGroupIndex;
112891         this.enable();
112892         view.store.group(hdr.dataIndex);
112893         this.pruneGroupedHeader();
112894         
112895     },
112896
112897     /**
112898      * Turn on and off grouping via the menu
112899      * @private
112900      */
112901     onGroupToggleMenuItemClick: function(menuItem, checked) {
112902         this[checked ? 'enable' : 'disable']();
112903     },
112904
112905     /**
112906      * Prunes the grouped header from the header container
112907      * @private
112908      */
112909     pruneGroupedHeader: function() {
112910         var me         = this,
112911             view       = me.view,
112912             store      = view.store,
112913             groupField = me.getGroupField(),
112914             headerCt   = view.headerCt,
112915             header     = headerCt.down('header[dataIndex=' + groupField + ']');
112916
112917         if (header) {
112918             if (me.prunedHeader) {
112919                 me.prunedHeader.show();
112920             }
112921             me.prunedHeader = header;
112922             header.hide();
112923         }
112924     },
112925
112926     getGroupField: function(){
112927         var group = this.view.store.groupers.first();
112928         if (group) {
112929             return group.property;    
112930         }
112931         return ''; 
112932     },
112933
112934     /**
112935      * When a row gains focus, expand the groups above it
112936      * @private
112937      */
112938     onRowFocus: function(rowIdx) {
112939         var node    = this.view.getNode(rowIdx),
112940             groupBd = Ext.fly(node).up('.' + this.collapsedCls);
112941
112942         if (groupBd) {
112943             // for multiple level groups, should expand every groupBd
112944             // above
112945             this.expand(groupBd);
112946         }
112947     },
112948
112949     /**
112950      * Expand a group by the groupBody
112951      * @param {Ext.core.Element} groupBd
112952      * @private
112953      */
112954     expand: function(groupBd) {
112955         var me = this,
112956             view = me.view,
112957             grid = view.up('gridpanel'),
112958             groupBdDom = Ext.getDom(groupBd);
112959             
112960         me.collapsedState[groupBdDom.id] = false;
112961
112962         groupBd.removeCls(me.collapsedCls);
112963         groupBd.prev().removeCls(me.hdCollapsedCls);
112964
112965         grid.determineScrollbars();
112966         grid.invalidateScroller();
112967         view.fireEvent('groupexpand');
112968     },
112969
112970     /**
112971      * Collapse a group by the groupBody
112972      * @param {Ext.core.Element} groupBd
112973      * @private
112974      */
112975     collapse: function(groupBd) {
112976         var me = this,
112977             view = me.view,
112978             grid = view.up('gridpanel'),
112979             groupBdDom = Ext.getDom(groupBd);
112980             
112981         me.collapsedState[groupBdDom.id] = true;
112982
112983         groupBd.addCls(me.collapsedCls);
112984         groupBd.prev().addCls(me.hdCollapsedCls);
112985
112986         grid.determineScrollbars();
112987         grid.invalidateScroller();
112988         view.fireEvent('groupcollapse');
112989     },
112990     
112991     onGroupChange: function(){
112992         this.view.refresh();
112993     },
112994
112995     /**
112996      * Toggle between expanded/collapsed state when clicking on
112997      * the group.
112998      * @private
112999      */
113000     onGroupClick: function(view, group, idx, foo, e) {
113001         var me = this,
113002             toggleCls = me.toggleCls,
113003             groupBd = Ext.fly(group.nextSibling, '_grouping');
113004
113005         if (groupBd.hasCls(me.collapsedCls)) {
113006             me.expand(groupBd);
113007         } else {
113008             me.collapse(groupBd);
113009         }
113010     },
113011
113012     // Injects isRow and closeRow into the metaRowTpl.
113013     getMetaRowTplFragments: function() {
113014         return {
113015             isRow: this.isRow,
113016             closeRow: this.closeRow
113017         };
113018     },
113019
113020     // injected into rowtpl and wrapped around metaRowTpl
113021     // becomes part of the standard tpl
113022     isRow: function() {
113023         return '<tpl if="typeof rows === \'undefined\'">';
113024     },
113025
113026     // injected into rowtpl and wrapped around metaRowTpl
113027     // becomes part of the standard tpl
113028     closeRow: function() {
113029         return '</tpl>';
113030     },
113031
113032     // isRow and closeRow are injected via getMetaRowTplFragments
113033     mutateMetaRowTpl: function(metaRowTpl) {
113034         metaRowTpl.unshift('{[this.isRow()]}');
113035         metaRowTpl.push('{[this.closeRow()]}');
113036     },
113037
113038     // injects an additional style attribute via tdAttrKey with the proper
113039     // amount of padding
113040     getAdditionalData: function(data, idx, record, orig) {
113041         var view = this.view,
113042             hCt  = view.headerCt,
113043             col  = hCt.items.getAt(0),
113044             o = {},
113045             tdAttrKey = col.id + '-tdAttr';
113046
113047         // maintain the current tdAttr that a user may ahve set.
113048         o[tdAttrKey] = this.indentByDepth(data) + " " + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
113049         o.collapsed = 'true';
113050         return o;
113051     },
113052
113053     // return matching preppedRecords
113054     getGroupRows: function(group, records, preppedRecords, fullWidth) {
113055         var me = this,
113056             children = group.children,
113057             rows = group.rows = [],
113058             view = me.view;
113059         group.viewId = view.id;
113060
113061         Ext.Array.each(records, function(record, idx) {
113062             if (Ext.Array.indexOf(children, record) != -1) {
113063                 rows.push(Ext.apply(preppedRecords[idx], {
113064                     depth: 1
113065                 }));
113066             }
113067         });
113068         delete group.children;
113069         group.fullWidth = fullWidth;
113070         if (me.collapsedState[view.id + '-gp-' + group.name]) {
113071             group.collapsedCls = me.collapsedCls;
113072             group.hdCollapsedCls = me.hdCollapsedCls;
113073         }
113074
113075         return group;
113076     },
113077
113078     // return the data in a grouped format.
113079     collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
113080         var me    = this,
113081             store = me.view.store,
113082             groups;
113083             
113084         if (!me.disabled && store.isGrouped()) {
113085             groups = store.getGroups();
113086             Ext.Array.each(groups, function(group, idx){
113087                 me.getGroupRows(group, records, preppedRecords, fullWidth);
113088             }, me);
113089             return {
113090                 rows: groups,
113091                 fullWidth: fullWidth
113092             };
113093         }
113094         return o;
113095     },
113096     
113097     // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
113098     // events that are fired on the view. Chose not to return the actual
113099     // group itself because of its expense and because developers can simply
113100     // grab the group via store.getGroups(groupName)
113101     getFireEventArgs: function(type, view, featureTarget, e) {
113102         var returnArray = [type, view, featureTarget],
113103             groupBd     = Ext.fly(featureTarget.nextSibling, '_grouping'),
113104             groupBdId   = Ext.getDom(groupBd).id,
113105             prefix      = view.id + '-gp-',
113106             groupName   = groupBdId.substr(prefix.length);
113107         
113108         returnArray.push(groupName, e);
113109         
113110         return returnArray;
113111     }
113112 });
113113
113114 /**
113115  * @class Ext.grid.feature.GroupingSummary
113116  * @extends Ext.grid.feature.Grouping
113117  * 
113118  * This feature adds an aggregate summary row at the bottom of each group that is provided
113119  * by the {@link Ext.grid.feature.Grouping} feature. There are 2 aspects to the summary:
113120  * 
113121  * ## Calculation
113122  * 
113123  * The summary value needs to be calculated for each column in the grid. This is controlled
113124  * by the summaryType option specified on the column. There are several built in summary types,
113125  * which can be specified as a string on the column configuration. These call underlying methods
113126  * on the store:
113127  *
113128  *  - {@link Ext.data.Store#count count}
113129  *  - {@link Ext.data.Store#sum sum}
113130  *  - {@link Ext.data.Store#min min}
113131  *  - {@link Ext.data.Store#max max}
113132  *  - {@link Ext.data.Store#average average}
113133  *
113134  * Alternatively, the summaryType can be a function definition. If this is the case,
113135  * the function is called with an array of records to calculate the summary value.
113136  * 
113137  * ## Rendering
113138  * 
113139  * Similar to a column, the summary also supports a summaryRenderer function. This
113140  * summaryRenderer is called before displaying a value. The function is optional, if
113141  * not specified the default calculated value is shown. The summaryRenderer is called with:
113142  *
113143  *  - value {Object} - The calculated value.
113144  *  - summaryData {Object} - Contains all raw summary values for the row.
113145  *  - field {String} - The name of the field we are calculating
113146  * 
113147  * ## Example Usage
113148  *
113149  *     Ext.define('TestResult', {
113150  *         extend: 'Ext.data.Model',
113151  *         fields: ['student', 'subject', {
113152  *             name: 'mark',
113153  *             type: 'int'
113154  *         }]
113155  *     });
113156  *     
113157  *     Ext.create('Ext.grid.Panel', {
113158  *         width: 200,
113159  *         height: 240,
113160  *         renderTo: document.body,
113161  *         features: [{
113162  *             groupHeaderTpl: 'Subject: {name}',
113163  *             ftype: 'groupingsummary'
113164  *         }],
113165  *         store: {
113166  *             model: 'TestResult',
113167  *             groupField: 'subject',
113168  *             data: [{
113169  *                 student: 'Student 1',
113170  *                 subject: 'Math',
113171  *                 mark: 84
113172  *             },{
113173  *                 student: 'Student 1',
113174  *                 subject: 'Science',
113175  *                 mark: 72
113176  *             },{
113177  *                 student: 'Student 2',
113178  *                 subject: 'Math',
113179  *                 mark: 96
113180  *             },{
113181  *                 student: 'Student 2',
113182  *                 subject: 'Science',
113183  *                 mark: 68
113184  *             }]
113185  *         },
113186  *         columns: [{
113187  *             dataIndex: 'student',
113188  *             text: 'Name',
113189  *             summaryType: 'count',
113190  *             summaryRenderer: function(value){
113191  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : ''); 
113192  *             }
113193  *         }, {
113194  *             dataIndex: 'mark',
113195  *             text: 'Mark',
113196  *             summaryType: 'average'
113197  *         }]
113198  *     });
113199  */
113200 Ext.define('Ext.grid.feature.GroupingSummary', {
113201     
113202     /* Begin Definitions */
113203     
113204     extend: 'Ext.grid.feature.Grouping',
113205     
113206     alias: 'feature.groupingsummary',
113207     
113208     mixins: {
113209         summary: 'Ext.grid.feature.AbstractSummary'
113210     },
113211     
113212     /* End Definitions */
113213
113214      
113215    /**
113216     * Modifies the row template to include the summary row.
113217     * @private
113218     * @return {String} The modified template
113219     */
113220    getFeatureTpl: function() {
113221         var tpl = this.callParent(arguments);
113222             
113223         if (this.showSummaryRow) {
113224             // lop off the end </tpl> so we can attach it
113225             tpl = tpl.replace('</tpl>', '');
113226             tpl += '{[this.printSummaryRow(xindex)]}</tpl>';
113227         }
113228         return tpl;
113229     },
113230     
113231     /**
113232      * Gets any fragments needed for the template.
113233      * @private
113234      * @return {Object} The fragments
113235      */
113236     getFragmentTpl: function() {
113237         var me = this,
113238             fragments = me.callParent();
113239             
113240         Ext.apply(fragments, me.getSummaryFragments());
113241         if (me.showSummaryRow) {
113242             // this gets called before render, so we'll setup the data here.
113243             me.summaryGroups = me.view.store.getGroups();
113244             me.summaryData = me.generateSummaryData();
113245         }
113246         return fragments;
113247     },
113248     
113249     /**
113250      * Gets the data for printing a template row
113251      * @private
113252      * @param {Number} index The index in the template
113253      * @return {Array} The template values
113254      */
113255     getPrintData: function(index){
113256         var me = this,
113257             columns = me.view.headerCt.getColumnsForTpl(),
113258             i = 0,
113259             length = columns.length,
113260             data = [],
113261             name = me.summaryGroups[index - 1].name,
113262             active = me.summaryData[name],
113263             column;
113264             
113265         for (; i < length; ++i) {
113266             column = columns[i];
113267             column.gridSummaryValue = this.getColumnValue(column, active);
113268             data.push(column);
113269         }
113270         return data;
113271     },
113272     
113273     /**
113274      * Generates all of the summary data to be used when processing the template
113275      * @private
113276      * @return {Object} The summary data
113277      */
113278     generateSummaryData: function(){
113279         var me = this,
113280             data = {},
113281             remoteData = {},
113282             store = me.view.store,
113283             groupField = this.getGroupField(),
113284             reader = store.proxy.reader,
113285             groups = me.summaryGroups,
113286             columns = me.view.headerCt.getColumnsForTpl(),
113287             i,
113288             length,
113289             fieldData,
113290             root,
113291             key,
113292             comp;
113293             
113294         for (i = 0, length = groups.length; i < length; ++i) {
113295             data[groups[i].name] = {};
113296         }
113297         
113298     /**
113299      * @cfg {String} remoteRoot.  The name of the property
113300      * which contains the Array of summary objects.  Defaults to <tt>undefined</tt>.
113301      * It allows to use server-side calculated summaries.
113302      */
113303         if (me.remoteRoot && reader.rawData) {
113304             // reset reader root and rebuild extractors to extract summaries data
113305             root = reader.root;
113306             reader.root = me.remoteRoot;
113307             reader.buildExtractors(true);
113308             Ext.Array.each(reader.getRoot(reader.rawData), function(value) {
113309                  data[value[groupField]] = value;
113310                  data[value[groupField]]._remote = true;
113311             });
113312             // restore initial reader configuration
113313             reader.root = root;
113314             reader.buildExtractors(true);
113315         }
113316         
113317         for (i = 0, length = columns.length; i < length; ++i) {
113318             comp = Ext.getCmp(columns[i].id);
113319             fieldData = me.getSummary(store, comp.summaryType, comp.dataIndex, true);
113320             
113321             for (key in fieldData) {
113322                 if (fieldData.hasOwnProperty(key)) {
113323                     if (!data[key]._remote) {
113324                         data[key][comp.dataIndex] = fieldData[key];
113325                     }
113326                 }
113327             }
113328         }
113329         return data;
113330     }
113331 });
113332
113333 /**
113334  * @class Ext.grid.feature.RowBody
113335  * @extends Ext.grid.feature.Feature
113336  *
113337  * The rowbody feature enhances the grid's markup to have an additional
113338  * tr -> td -> div which spans the entire width of the original row.
113339  *
113340  * This is useful to to associate additional information with a particular
113341  * record in a grid.
113342  *
113343  * Rowbodies are initially hidden unless you override getAdditionalData.
113344  *
113345  * Will expose additional events on the gridview with the prefix of 'rowbody'.
113346  * For example: 'rowbodyclick', 'rowbodydblclick', 'rowbodycontextmenu'.
113347  *
113348  * @ftype rowbody
113349  */
113350 Ext.define('Ext.grid.feature.RowBody', {
113351     extend: 'Ext.grid.feature.Feature',
113352     alias: 'feature.rowbody',
113353     rowBodyHiddenCls: Ext.baseCSSPrefix + 'grid-row-body-hidden',
113354     rowBodyTrCls: Ext.baseCSSPrefix + 'grid-rowbody-tr',
113355     rowBodyTdCls: Ext.baseCSSPrefix + 'grid-cell-rowbody',
113356     rowBodyDivCls: Ext.baseCSSPrefix + 'grid-rowbody',
113357
113358     eventPrefix: 'rowbody',
113359     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-rowbody-tr',
113360     
113361     getRowBody: function(values) {
113362         return [
113363             '<tr class="' + this.rowBodyTrCls + ' {rowBodyCls}">',
113364                 '<td class="' + this.rowBodyTdCls + '" colspan="{rowBodyColspan}">',
113365                     '<div class="' + this.rowBodyDivCls + '">{rowBody}</div>',
113366                 '</td>',
113367             '</tr>'
113368         ].join('');
113369     },
113370     
113371     // injects getRowBody into the metaRowTpl.
113372     getMetaRowTplFragments: function() {
113373         return {
113374             getRowBody: this.getRowBody,
113375             rowBodyTrCls: this.rowBodyTrCls,
113376             rowBodyTdCls: this.rowBodyTdCls,
113377             rowBodyDivCls: this.rowBodyDivCls
113378         };
113379     },
113380
113381     mutateMetaRowTpl: function(metaRowTpl) {
113382         metaRowTpl.push('{[this.getRowBody(values)]}');
113383     },
113384
113385     /**
113386      * Provide additional data to the prepareData call within the grid view.
113387      * The rowbody feature adds 3 additional variables into the grid view's template.
113388      * These are rowBodyCls, rowBodyColspan, and rowBody.
113389      * @param {Object} data The data for this particular record.
113390      * @param {Number} idx The row index for this record.
113391      * @param {Ext.data.Model} record The record instance
113392      * @param {Object} orig The original result from the prepareData call to massage.
113393      */
113394     getAdditionalData: function(data, idx, record, orig) {
113395         var headerCt = this.view.headerCt,
113396             colspan  = headerCt.getColumnCount();
113397
113398         return {
113399             rowBody: "",
113400             rowBodyCls: this.rowBodyCls,
113401             rowBodyColspan: colspan
113402         };
113403     }
113404 });
113405 /**
113406  * @class Ext.grid.feature.RowWrap
113407  * @extends Ext.grid.feature.Feature
113408  * @private
113409  */
113410 Ext.define('Ext.grid.feature.RowWrap', {
113411     extend: 'Ext.grid.feature.Feature',
113412     alias: 'feature.rowwrap',
113413
113414     // turn off feature events.
113415     hasFeatureEvent: false,
113416     
113417     mutateMetaRowTpl: function(metaRowTpl) {        
113418         // Remove "x-grid-row" from the first row, note this could be wrong
113419         // if some other feature unshifted things in front.
113420         metaRowTpl[0] = metaRowTpl[0].replace(Ext.baseCSSPrefix + 'grid-row', '');
113421         metaRowTpl[0] = metaRowTpl[0].replace("{[this.embedRowCls()]}", "");
113422         // 2
113423         metaRowTpl.unshift('<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" style="width: {[this.embedFullWidth()]}px;">');
113424         // 1
113425         metaRowTpl.unshift('<tr class="' + Ext.baseCSSPrefix + 'grid-row {[this.embedRowCls()]}"><td colspan="{[this.embedColSpan()]}"><div class="' + Ext.baseCSSPrefix + 'grid-rowwrap-div">');
113426         
113427         // 3
113428         metaRowTpl.push('</table>');
113429         // 4
113430         metaRowTpl.push('</div></td></tr>');
113431     },
113432     
113433     embedColSpan: function() {
113434         return '{colspan}';
113435     },
113436     
113437     embedFullWidth: function() {
113438         return '{fullWidth}';
113439     },
113440     
113441     getAdditionalData: function(data, idx, record, orig) {
113442         var headerCt = this.view.headerCt,
113443             colspan  = headerCt.getColumnCount(),
113444             fullWidth = headerCt.getFullWidth(),
113445             items    = headerCt.query('gridcolumn'),
113446             itemsLn  = items.length,
113447             i = 0,
113448             o = {
113449                 colspan: colspan,
113450                 fullWidth: fullWidth
113451             },
113452             id,
113453             tdClsKey,
113454             colResizerCls;
113455
113456         for (; i < itemsLn; i++) {
113457             id = items[i].id;
113458             tdClsKey = id + '-tdCls';
113459             colResizerCls = Ext.baseCSSPrefix + 'grid-col-resizer-'+id;
113460             // give the inner td's the resizer class
113461             // while maintaining anything a user may have injected via a custom
113462             // renderer
113463             o[tdClsKey] = colResizerCls + " " + (orig[tdClsKey] ? orig[tdClsKey] : '');
113464             // TODO: Unhackify the initial rendering width's
113465             o[id+'-tdAttr'] = " style=\"width: " + (items[i].hidden ? 0 : items[i].getDesiredWidth()) + "px;\" "/* + (i === 0 ? " rowspan=\"2\"" : "")*/;
113466             if (orig[id+'-tdAttr']) {
113467                 o[id+'-tdAttr'] += orig[id+'-tdAttr'];
113468             }
113469             
113470         }
113471
113472         return o;
113473     },
113474     
113475     getMetaRowTplFragments: function() {
113476         return {
113477             embedFullWidth: this.embedFullWidth,
113478             embedColSpan: this.embedColSpan
113479         };
113480     }
113481     
113482 });
113483 /**
113484  * @class Ext.grid.feature.Summary
113485  * @extends Ext.grid.feature.AbstractSummary
113486  * 
113487  * This feature is used to place a summary row at the bottom of the grid. If using a grouping, 
113488  * see {@link Ext.grid.feature.GroupingSummary}. There are 2 aspects to calculating the summaries, 
113489  * calculation and rendering.
113490  * 
113491  * ## Calculation
113492  * The summary value needs to be calculated for each column in the grid. This is controlled
113493  * by the summaryType option specified on the column. There are several built in summary types,
113494  * which can be specified as a string on the column configuration. These call underlying methods
113495  * on the store:
113496  *
113497  *  - {@link Ext.data.Store#count count}
113498  *  - {@link Ext.data.Store#sum sum}
113499  *  - {@link Ext.data.Store#min min}
113500  *  - {@link Ext.data.Store#max max}
113501  *  - {@link Ext.data.Store#average average}
113502  *
113503  * Alternatively, the summaryType can be a function definition. If this is the case,
113504  * the function is called with an array of records to calculate the summary value.
113505  * 
113506  * ## Rendering
113507  * Similar to a column, the summary also supports a summaryRenderer function. This
113508  * summaryRenderer is called before displaying a value. The function is optional, if
113509  * not specified the default calculated value is shown. The summaryRenderer is called with:
113510  *
113511  *  - value {Object} - The calculated value.
113512  *  - summaryData {Object} - Contains all raw summary values for the row.
113513  *  - field {String} - The name of the field we are calculating
113514  * 
113515  * ## Example Usage
113516  *
113517  *     Ext.define('TestResult', {
113518  *         extend: 'Ext.data.Model',
113519  *         fields: ['student', {
113520  *             name: 'mark',
113521  *             type: 'int'
113522  *         }]
113523  *     });
113524  *     
113525  *     Ext.create('Ext.grid.Panel', {
113526  *         width: 200,
113527  *         height: 140,
113528  *         renderTo: document.body,
113529  *         features: [{
113530  *             ftype: 'summary'
113531  *         }],
113532  *         store: {
113533  *             model: 'TestResult',
113534  *             data: [{
113535  *                 student: 'Student 1',
113536  *                 mark: 84
113537  *             },{
113538  *                 student: 'Student 2',
113539  *                 mark: 72
113540  *             },{
113541  *                 student: 'Student 3',
113542  *                 mark: 96
113543  *             },{
113544  *                 student: 'Student 4',
113545  *                 mark: 68
113546  *             }]
113547  *         },
113548  *         columns: [{
113549  *             dataIndex: 'student',
113550  *             text: 'Name',
113551  *             summaryType: 'count',
113552  *             summaryRenderer: function(value, summaryData, dataIndex) {
113553  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : ''); 
113554  *             }
113555  *         }, {
113556  *             dataIndex: 'mark',
113557  *             text: 'Mark',
113558  *             summaryType: 'average'
113559  *         }]
113560  *     });
113561  */
113562 Ext.define('Ext.grid.feature.Summary', {
113563     
113564     /* Begin Definitions */
113565     
113566     extend: 'Ext.grid.feature.AbstractSummary',
113567     
113568     alias: 'feature.summary',
113569     
113570     /* End Definitions */
113571     
113572     /**
113573      * Gets any fragments needed for the template.
113574      * @private
113575      * @return {Object} The fragments
113576      */
113577     getFragmentTpl: function() {
113578         // this gets called before render, so we'll setup the data here.
113579         this.summaryData = this.generateSummaryData(); 
113580         return this.getSummaryFragments();
113581     },
113582     
113583     /**
113584      * Overrides the closeRows method on the template so we can include our own custom
113585      * footer.
113586      * @private
113587      * @return {Object} The custom fragments
113588      */
113589     getTableFragments: function(){
113590         if (this.showSummaryRow) {
113591             return {
113592                 closeRows: this.closeRows
113593             };
113594         }
113595     },
113596     
113597     /**
113598      * Provide our own custom footer for the grid.
113599      * @private
113600      * @return {String} The custom footer
113601      */
113602     closeRows: function() {
113603         return '</tpl>{[this.printSummaryRow()]}';
113604     },
113605     
113606     /**
113607      * Gets the data for printing a template row
113608      * @private
113609      * @param {Number} index The index in the template
113610      * @return {Array} The template values
113611      */
113612     getPrintData: function(index){
113613         var me = this,
113614             columns = me.view.headerCt.getColumnsForTpl(),
113615             i = 0,
113616             length = columns.length,
113617             data = [],
113618             active = me.summaryData,
113619             column;
113620             
113621         for (; i < length; ++i) {
113622             column = columns[i];
113623             column.gridSummaryValue = this.getColumnValue(column, active);
113624             data.push(column);
113625         }
113626         return data;
113627     },
113628     
113629     /**
113630      * Generates all of the summary data to be used when processing the template
113631      * @private
113632      * @return {Object} The summary data
113633      */
113634     generateSummaryData: function(){
113635         var me = this,
113636             data = {},
113637             store = me.view.store,
113638             columns = me.view.headerCt.getColumnsForTpl(),
113639             i = 0,
113640             length = columns.length,
113641             fieldData,
113642             key,
113643             comp;
113644             
113645         for (i = 0, length = columns.length; i < length; ++i) {
113646             comp = Ext.getCmp(columns[i].id);
113647             data[comp.dataIndex] = me.getSummary(store, comp.summaryType, comp.dataIndex, false);
113648         }
113649         return data;
113650     }
113651 });
113652 /**
113653  * @class Ext.grid.header.DragZone
113654  * @extends Ext.dd.DragZone
113655  * @private
113656  */
113657 Ext.define('Ext.grid.header.DragZone', {
113658     extend: 'Ext.dd.DragZone',
113659     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
113660     maxProxyWidth: 120,
113661
113662     constructor: function(headerCt) {
113663         this.headerCt = headerCt;
113664         this.ddGroup =  this.getDDGroup();
113665         this.callParent([headerCt.el]);
113666         this.proxy.el.addCls(Ext.baseCSSPrefix + 'grid-col-dd');
113667     },
113668
113669     getDDGroup: function() {
113670         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
113671     },
113672
113673     getDragData: function(e) {
113674         var header = e.getTarget('.'+this.colHeaderCls),
113675             headerCmp;
113676
113677         if (header) {
113678             headerCmp = Ext.getCmp(header.id);
113679             if (!this.headerCt.dragging && headerCmp.draggable && !(headerCmp.isOnLeftEdge(e) || headerCmp.isOnRightEdge(e))) {
113680                 var ddel = document.createElement('div');
113681                 ddel.innerHTML = Ext.getCmp(header.id).text;
113682                 return {
113683                     ddel: ddel,
113684                     header: headerCmp
113685                 };
113686             }
113687         }
113688         return false;
113689     },
113690
113691     onBeforeDrag: function() {
113692         return !(this.headerCt.dragging || this.disabled);
113693     },
113694
113695     onInitDrag: function() {
113696         this.headerCt.dragging = true;
113697         this.callParent(arguments);
113698     },
113699
113700     onDragDrop: function() {
113701         this.headerCt.dragging = false;
113702         this.callParent(arguments);
113703     },
113704
113705     afterRepair: function() {
113706         this.callParent();
113707         this.headerCt.dragging = false;
113708     },
113709
113710     getRepairXY: function() {
113711         return this.dragData.header.el.getXY();
113712     },
113713     
113714     disable: function() {
113715         this.disabled = true;
113716     },
113717     
113718     enable: function() {
113719         this.disabled = false;
113720     }
113721 });
113722
113723 /**
113724  * @class Ext.grid.header.DropZone
113725  * @extends Ext.dd.DropZone
113726  * @private
113727  */
113728 Ext.define('Ext.grid.header.DropZone', {
113729     extend: 'Ext.dd.DropZone',
113730     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
113731     proxyOffsets: [-4, -9],
113732
113733     constructor: function(headerCt){
113734         this.headerCt = headerCt;
113735         this.ddGroup = this.getDDGroup();
113736         this.callParent([headerCt.el]);
113737     },
113738
113739     getDDGroup: function() {
113740         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
113741     },
113742
113743     getTargetFromEvent : function(e){
113744         return e.getTarget('.' + this.colHeaderCls);
113745     },
113746
113747     getTopIndicator: function() {
113748         if (!this.topIndicator) {
113749             this.topIndicator = Ext.core.DomHelper.append(Ext.getBody(), {
113750                 cls: "col-move-top",
113751                 html: "&#160;"
113752             }, true);
113753         }
113754         return this.topIndicator;
113755     },
113756
113757     getBottomIndicator: function() {
113758         if (!this.bottomIndicator) {
113759             this.bottomIndicator = Ext.core.DomHelper.append(Ext.getBody(), {
113760                 cls: "col-move-bottom",
113761                 html: "&#160;"
113762             }, true);
113763         }
113764         return this.bottomIndicator;
113765     },
113766
113767     getLocation: function(e, t) {
113768         var x      = e.getXY()[0],
113769             region = Ext.fly(t).getRegion(),
113770             pos, header;
113771
113772         if ((region.right - x) <= (region.right - region.left) / 2) {
113773             pos = "after";
113774         } else {
113775             pos = "before";
113776         }
113777         return {
113778             pos: pos,
113779             header: Ext.getCmp(t.id),
113780             node: t
113781         };
113782     },
113783
113784     positionIndicator: function(draggedHeader, node, e){
113785         var location = this.getLocation(e, node),
113786             header = location.header,
113787             pos    = location.pos,
113788             nextHd = draggedHeader.nextSibling('gridcolumn:not([hidden])'),
113789             prevHd = draggedHeader.previousSibling('gridcolumn:not([hidden])'),
113790             region, topIndicator, bottomIndicator, topAnchor, bottomAnchor,
113791             topXY, bottomXY, headerCtEl, minX, maxX;
113792
113793         // Cannot drag beyond non-draggable start column
113794         if (!header.draggable && header.getIndex() == 0) {
113795             return false;
113796         }
113797
113798         this.lastLocation = location;
113799
113800         if ((draggedHeader !== header) &&
113801             ((pos === "before" && nextHd !== header) ||
113802             (pos === "after" && prevHd !== header)) &&
113803             !header.isDescendantOf(draggedHeader)) {
113804
113805             // As we move in between different DropZones that are in the same
113806             // group (such as the case when in a locked grid), invalidateDrop
113807             // on the other dropZones.
113808             var allDropZones = Ext.dd.DragDropManager.getRelated(this),
113809                 ln = allDropZones.length,
113810                 i  = 0,
113811                 dropZone;
113812
113813             for (; i < ln; i++) {
113814                 dropZone = allDropZones[i];
113815                 if (dropZone !== this && dropZone.invalidateDrop) {
113816                     dropZone.invalidateDrop();
113817                 }
113818             }
113819
113820
113821             this.valid = true;
113822             topIndicator = this.getTopIndicator();
113823             bottomIndicator = this.getBottomIndicator();
113824             if (pos === 'before') {
113825                 topAnchor = 'tl';
113826                 bottomAnchor = 'bl';
113827             } else {
113828                 topAnchor = 'tr';
113829                 bottomAnchor = 'br';
113830             }
113831             topXY = header.el.getAnchorXY(topAnchor);
113832             bottomXY = header.el.getAnchorXY(bottomAnchor);
113833
113834             // constrain the indicators to the viewable section
113835             headerCtEl = this.headerCt.el;
113836             minX = headerCtEl.getLeft();
113837             maxX = headerCtEl.getRight();
113838
113839             topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
113840             bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);
113841
113842             // adjust by offsets, this is to center the arrows so that they point
113843             // at the split point
113844             topXY[0] -= 4;
113845             topXY[1] -= 9;
113846             bottomXY[0] -= 4;
113847
113848             // position and show indicators
113849             topIndicator.setXY(topXY);
113850             bottomIndicator.setXY(bottomXY);
113851             topIndicator.show();
113852             bottomIndicator.show();
113853         // invalidate drop operation and hide indicators
113854         } else {
113855             this.invalidateDrop();
113856         }
113857     },
113858
113859     invalidateDrop: function() {
113860         this.valid = false;
113861         this.hideIndicators();
113862     },
113863
113864     onNodeOver: function(node, dragZone, e, data) {
113865         if (data.header.el.dom !== node) {
113866             this.positionIndicator(data.header, node, e);
113867         }
113868         return this.valid ? this.dropAllowed : this.dropNotAllowed;
113869     },
113870
113871     hideIndicators: function() {
113872         this.getTopIndicator().hide();
113873         this.getBottomIndicator().hide();
113874     },
113875
113876     onNodeOut: function() {
113877         this.hideIndicators();
113878     },
113879
113880     onNodeDrop: function(node, dragZone, e, data) {
113881         if (this.valid) {
113882             this.invalidateDrop();
113883             var hd = data.header,
113884                 lastLocation = this.lastLocation,
113885                 fromCt  = hd.ownerCt,
113886                 fromIdx = fromCt.items.indexOf(hd), // Container.items is a MixedCollection
113887                 toCt    = lastLocation.header.ownerCt,
113888                 toIdx   = toCt.items.indexOf(lastLocation.header),
113889                 headerCt = this.headerCt,
113890                 groupCt,
113891                 scrollerOwner;
113892
113893             if (lastLocation.pos === 'after') {
113894                 toIdx++;
113895             }
113896
113897             // If we are dragging in between two HeaderContainers that have had the lockable
113898             // mixin injected we will lock/unlock headers in between sections. Note that lockable
113899             // does NOT currently support grouped headers.
113900             if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && toCt.lockedCt) {
113901                 scrollerOwner = fromCt.up('[scrollerOwner]');
113902                 scrollerOwner.lock(hd, toIdx);
113903             } else if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && fromCt.lockedCt) {
113904                 scrollerOwner = fromCt.up('[scrollerOwner]');
113905                 scrollerOwner.unlock(hd, toIdx);
113906             } else {
113907                 // If dragging rightwards, then after removal, the insertion index will be one less when moving
113908                 // in between the same container.
113909                 if ((fromCt === toCt) && (toIdx > fromCt.items.indexOf(hd))) {
113910                     toIdx--;
113911                 }
113912
113913                 // Remove dragged header from where it was without destroying it or relaying its Container
113914                 if (fromCt !== toCt) {
113915                     fromCt.suspendLayout = true;
113916                     fromCt.remove(hd, false);
113917                     fromCt.suspendLayout = false;
113918                 }
113919
113920                 // Dragged the last header out of the fromCt group... The fromCt group must die
113921                 if (fromCt.isGroupHeader) {
113922                     if (!fromCt.items.getCount()) {
113923                         groupCt = fromCt.ownerCt;
113924                         groupCt.suspendLayout = true;
113925                         groupCt.remove(fromCt, false);
113926                         fromCt.el.dom.parentNode.removeChild(fromCt.el.dom);
113927                         groupCt.suspendLayout = false;
113928                     } else {
113929                         fromCt.minWidth = fromCt.getWidth() - hd.getWidth();
113930                         fromCt.setWidth(fromCt.minWidth);
113931                     }
113932                 }
113933
113934                 // Move dragged header into its drop position
113935                 toCt.suspendLayout = true;
113936                 if (fromCt === toCt) {
113937                     toCt.move(fromIdx, toIdx);
113938                 } else {
113939                     toCt.insert(toIdx, hd);
113940                 }
113941                 toCt.suspendLayout = false;
113942
113943                 // Group headers acquire the aggregate width of their child headers
113944                 // Therefore a child header may not flex; it must contribute a fixed width.
113945                 // But we restore the flex value when moving back into the main header container
113946                 if (toCt.isGroupHeader) {
113947                     hd.savedFlex = hd.flex;
113948                     delete hd.flex;
113949                     hd.width = hd.getWidth();
113950                     // When there was previously a flex, we need to ensure we don't count for the
113951                     // border twice.
113952                     toCt.minWidth = toCt.getWidth() + hd.getWidth() - (hd.savedFlex ? 1 : 0);
113953                     toCt.setWidth(toCt.minWidth);
113954                 } else {
113955                     if (hd.savedFlex) {
113956                         hd.flex = hd.savedFlex;
113957                         delete hd.width;
113958                     }
113959                 }
113960
113961
113962                 // Refresh columns cache in case we remove an emptied group column
113963                 headerCt.purgeCache();
113964                 headerCt.doLayout();
113965                 headerCt.onHeaderMoved(hd, fromIdx, toIdx);
113966                 // Emptied group header can only be destroyed after the header and grid have been refreshed
113967                 if (!fromCt.items.getCount()) {
113968                     fromCt.destroy();
113969                 }
113970             }
113971         }
113972     }
113973 });
113974
113975
113976 /**
113977  * @class Ext.grid.plugin.Editing
113978
113979 This class provides an abstract grid editing plugin on selected {@link Ext.grid.column.Column columns}.
113980 The editable columns are specified by providing an {@link Ext.grid.column.Column#editor editor}
113981 in the {@link Ext.grid.column.Column column configuration}.
113982
113983 *Note:* This class should not be used directly. See {@link Ext.grid.plugin.CellEditing} and
113984 {@link Ext.grid.plugin.RowEditing}.
113985
113986  * @markdown
113987  */
113988 Ext.define('Ext.grid.plugin.Editing', {
113989     alias: 'editing.editing',
113990
113991     requires: [
113992         'Ext.grid.column.Column',
113993         'Ext.util.KeyNav'
113994     ],
113995
113996     mixins: {
113997         observable: 'Ext.util.Observable'
113998     },
113999
114000     /**
114001      * @cfg {Number} clicksToEdit
114002      * The number of clicks on a grid required to display the editor (defaults to 2).
114003      */
114004     clicksToEdit: 2,
114005
114006     // private
114007     defaultFieldXType: 'textfield',
114008
114009     // cell, row, form
114010     editStyle: '',
114011
114012     constructor: function(config) {
114013         var me = this;
114014         Ext.apply(me, config);
114015
114016         me.addEvents(
114017             // Doc'ed in separate editing plugins
114018             'beforeedit',
114019
114020             // Doc'ed in separate editing plugins
114021             'edit',
114022
114023             // Doc'ed in separate editing plugins
114024             'validateedit'
114025         );
114026         me.mixins.observable.constructor.call(me);
114027         // TODO: Deprecated, remove in 5.0
114028         me.relayEvents(me, ['afteredit'], 'after');
114029     },
114030
114031     // private
114032     init: function(grid) {
114033         var me = this;
114034
114035         me.grid = grid;
114036         me.view = grid.view;
114037         me.initEvents();
114038         me.initFieldAccessors(me.view.getGridColumns());
114039
114040         grid.relayEvents(me, ['beforeedit', 'edit', 'validateedit']);
114041         // Marks the grid as editable, so that the SelectionModel
114042         // can make appropriate decisions during navigation
114043         grid.isEditable = true;
114044         grid.editingPlugin = grid.view.editingPlugin = me;
114045     },
114046
114047     /**
114048      * @private
114049      * AbstractComponent calls destroy on all its plugins at destroy time.
114050      */
114051     destroy: function() {
114052         var me = this,
114053             grid = me.grid,
114054             headerCt = grid.headerCt,
114055             events = grid.events;
114056
114057         Ext.destroy(me.keyNav);
114058         me.removeFieldAccessors(grid.getView().getGridColumns());
114059
114060         // Clear all listeners from all our events, clear all managed listeners we added to other Observables
114061         me.clearListeners();
114062
114063         delete me.grid.editingPlugin;
114064         delete me.grid.view.editingPlugin;
114065         delete me.grid;
114066         delete me.view;
114067         delete me.editor;
114068         delete me.keyNav;
114069     },
114070
114071     // private
114072     getEditStyle: function() {
114073         return this.editStyle;
114074     },
114075
114076     // private
114077     initFieldAccessors: function(column) {
114078         var me = this;
114079
114080         if (Ext.isArray(column)) {
114081             Ext.Array.forEach(column, me.initFieldAccessors, me);
114082             return;
114083         }
114084
114085         // Augment the Header class to have a getEditor and setEditor method
114086         // Important: Only if the header does not have its own implementation.
114087         Ext.applyIf(column, {
114088             getEditor: function(record, defaultField) {
114089                 return me.getColumnField(this, defaultField);
114090             },
114091
114092             setEditor: function(field) {
114093                 me.setColumnField(this, field);
114094             }
114095         });
114096     },
114097
114098     // private
114099     removeFieldAccessors: function(column) {
114100         var me = this;
114101
114102         if (Ext.isArray(column)) {
114103             Ext.Array.forEach(column, me.removeFieldAccessors, me);
114104             return;
114105         }
114106
114107         delete column.getEditor;
114108         delete column.setEditor;
114109     },
114110
114111     // private
114112     // remaps to the public API of Ext.grid.column.Column.getEditor
114113     getColumnField: function(columnHeader, defaultField) {
114114         var field = columnHeader.field;
114115
114116         if (!field && columnHeader.editor) {
114117             field = columnHeader.editor;
114118             delete columnHeader.editor;
114119         }
114120
114121         if (!field && defaultField) {
114122             field = defaultField;
114123         }
114124
114125         if (field) {
114126             if (Ext.isString(field)) {
114127                 field = { xtype: field };
114128             }
114129             if (Ext.isObject(field) && !field.isFormField) {
114130                 field = Ext.ComponentManager.create(field, this.defaultFieldXType);
114131                 columnHeader.field = field;
114132             }
114133
114134             Ext.apply(field, {
114135                 name: columnHeader.dataIndex
114136             });
114137
114138             return field;
114139         }
114140     },
114141
114142     // private
114143     // remaps to the public API of Ext.grid.column.Column.setEditor
114144     setColumnField: function(column, field) {
114145         if (Ext.isObject(field) && !field.isFormField) {
114146             field = Ext.ComponentManager.create(field, this.defaultFieldXType);
114147         }
114148         column.field = field;
114149     },
114150
114151     // private
114152     initEvents: function() {
114153         var me = this;
114154         me.initEditTriggers();
114155         me.initCancelTriggers();
114156     },
114157
114158     // @abstract
114159     initCancelTriggers: Ext.emptyFn,
114160
114161     // private
114162     initEditTriggers: function() {
114163         var me = this,
114164             view = me.view,
114165             clickEvent = me.clicksToEdit === 1 ? 'click' : 'dblclick';
114166
114167         // Start editing
114168         me.mon(view, 'cell' + clickEvent, me.startEditByClick, me);
114169         view.on('render', function() {
114170             me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
114171                 enter: me.onEnterKey,
114172                 esc: me.onEscKey,
114173                 scope: me
114174             });
114175         }, me, { single: true });
114176     },
114177
114178     // private
114179     onEnterKey: function(e) {
114180         var me = this,
114181             grid = me.grid,
114182             selModel = grid.getSelectionModel(),
114183             record,
114184             columnHeader = grid.headerCt.getHeaderAtIndex(0);
114185
114186         // Calculate editing start position from SelectionModel
114187         // CellSelectionModel
114188         if (selModel.getCurrentPosition) {
114189             pos = selModel.getCurrentPosition();
114190             record = grid.store.getAt(pos.row);
114191             columnHeader = grid.headerCt.getHeaderAtIndex(pos.column);
114192         }
114193         // RowSelectionModel
114194         else {
114195             record = selModel.getLastSelected();
114196         }
114197         me.startEdit(record, columnHeader);
114198     },
114199
114200     // private
114201     onEscKey: function(e) {
114202         this.cancelEdit();
114203     },
114204
114205     // private
114206     startEditByClick: function(view, cell, colIdx, record, row, rowIdx, e) {
114207         this.startEdit(record, view.getHeaderAtIndex(colIdx));
114208     },
114209
114210     /**
114211      * @private
114212      * @abstract. Template method called before editing begins.
114213      * @param {Object} context The current editing context
114214      * @return {Boolean} Return false to cancel the editing process
114215      */
114216     beforeEdit: Ext.emptyFn,
114217
114218     /**
114219      * Start editing the specified record, using the specified Column definition to define which field is being edited.
114220      * @param {Model} record The Store data record which backs the row to be edited.
114221      * @param {Model} columnHeader The Column object defining the column to be edited.
114222      */
114223     startEdit: function(record, columnHeader) {
114224         var me = this,
114225             context = me.getEditingContext(record, columnHeader);
114226
114227         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
114228             return false;
114229         }
114230
114231         me.context = context;
114232         me.editing = true;
114233     },
114234
114235     /**
114236      * @private Collects all information necessary for any subclasses to perform their editing functions.
114237      * @param record
114238      * @param columnHeader
114239      * @returns {Object} The editing context based upon the passed record and column
114240      */
114241     getEditingContext: function(record, columnHeader) {
114242         var me = this,
114243             grid = me.grid,
114244             store = grid.store,
114245             rowIdx,
114246             colIdx,
114247             view = grid.getView(),
114248             value;
114249
114250         // If they'd passed numeric row, column indices, look them up.
114251         if (Ext.isNumber(record)) {
114252             rowIdx = record;
114253             record = store.getAt(rowIdx);
114254         } else {
114255             rowIdx = store.indexOf(record);
114256         }
114257         if (Ext.isNumber(columnHeader)) {
114258             colIdx = columnHeader;
114259             columnHeader = grid.headerCt.getHeaderAtIndex(colIdx);
114260         } else {
114261             colIdx = columnHeader.getIndex();
114262         }
114263
114264         value = record.get(columnHeader.dataIndex);
114265         return {
114266             grid: grid,
114267             record: record,
114268             field: columnHeader.dataIndex,
114269             value: value,
114270             row: view.getNode(rowIdx),
114271             column: columnHeader,
114272             rowIdx: rowIdx,
114273             colIdx: colIdx
114274         };
114275     },
114276
114277     /**
114278      * Cancel any active edit that is in progress.
114279      */
114280     cancelEdit: function() {
114281         this.editing = false;
114282     },
114283
114284     /**
114285      * Complete the edit if there is an active edit in progress.
114286      */
114287     completeEdit: function() {
114288         var me = this;
114289
114290         if (me.editing && me.validateEdit()) {
114291             me.fireEvent('edit', me.context);
114292         }
114293
114294         delete me.context;
114295         me.editing = false;
114296     },
114297
114298     // @abstract
114299     validateEdit: function() {
114300         var me = this,
114301             context = me.context;
114302
114303         return me.fireEvent('validateedit', me, context) !== false && !context.cancel;
114304     }
114305 });
114306 /**
114307  * @class Ext.grid.plugin.CellEditing
114308  * @extends Ext.grid.plugin.Editing
114309  *
114310  * The Ext.grid.plugin.CellEditing plugin injects editing at a cell level for a Grid. Only a single
114311  * cell will be editable at a time. The field that will be used for the editor is defined at the
114312  * {@link Ext.grid.column.Column#field field}. The editor can be a field instance or a field configuration.
114313  *
114314  * If an editor is not specified for a particular column then that cell will not be editable and it will
114315  * be skipped when activated via the mouse or the keyboard.
114316  *
114317  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
114318  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
114319  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
114320  *
114321  * {@img Ext.grid.plugin.CellEditing/Ext.grid.plugin.CellEditing.png Ext.grid.plugin.CellEditing plugin}
114322  *
114323  * ## Example Usage
114324  *
114325  *     Ext.create('Ext.data.Store', {
114326  *         storeId:'simpsonsStore',
114327  *         fields:['name', 'email', 'phone'],
114328  *         data:{'items':[
114329  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
114330  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
114331  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
114332  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
114333  *         ]},
114334  *         proxy: {
114335  *             type: 'memory',
114336  *             reader: {
114337  *                 type: 'json',
114338  *                 root: 'items'
114339  *             }
114340  *         }
114341  *     });
114342  *     
114343  *     Ext.create('Ext.grid.Panel', {
114344  *         title: 'Simpsons',
114345  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
114346  *         columns: [
114347  *             {header: 'Name',  dataIndex: 'name', field: 'textfield'},
114348  *             {header: 'Email', dataIndex: 'email', flex:1,
114349  *                 editor: {
114350  *                     xtype:'textfield',
114351  *                     allowBlank:false
114352  *                 }
114353  *             },
114354  *             {header: 'Phone', dataIndex: 'phone'}
114355  *         ],
114356  *         selType: 'cellmodel',
114357  *         plugins: [
114358  *             Ext.create('Ext.grid.plugin.CellEditing', {
114359  *                 clicksToEdit: 1
114360  *             })
114361  *         ],
114362  *         height: 200,
114363  *         width: 400,
114364  *         renderTo: Ext.getBody()
114365  *     });
114366  */
114367 Ext.define('Ext.grid.plugin.CellEditing', {
114368     alias: 'plugin.cellediting',
114369     extend: 'Ext.grid.plugin.Editing',
114370     requires: ['Ext.grid.CellEditor'],
114371
114372     constructor: function() {
114373         /**
114374          * @event beforeedit
114375          * Fires before cell editing is triggered. The edit event object has the following properties <br />
114376          * <ul style="padding:5px;padding-left:16px;">
114377          * <li>grid - The grid</li>
114378          * <li>record - The record being edited</li>
114379          * <li>field - The field name being edited</li>
114380          * <li>value - The value for the field being edited.</li>
114381          * <li>row - The grid table row</li>
114382          * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.</li>
114383          * <li>rowIdx - The row index that is being edited</li>
114384          * <li>colIdx - The column index that is being edited</li>
114385          * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
114386          * </ul>
114387          * @param {Ext.grid.plugin.Editing} editor
114388          * @param {Object} e An edit event (see above for description)
114389          */
114390         /**
114391          * @event edit
114392          * Fires after a cell is edited. The edit event object has the following properties <br />
114393          * <ul style="padding:5px;padding-left:16px;">
114394          * <li>grid - The grid</li>
114395          * <li>record - The record that was edited</li>
114396          * <li>field - The field name that was edited</li>
114397          * <li>value - The value being set</li>
114398          * <li>originalValue - The original value for the field, before the edit.</li>
114399          * <li>row - The grid table row</li>
114400          * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.</li>
114401          * <li>rowIdx - The row index that was edited</li>
114402          * <li>colIdx - The column index that was edited</li>
114403          * </ul>
114404          *
114405          * <pre><code>
114406 grid.on('edit', onEdit, this);
114407
114408 function onEdit(e) {
114409     // execute an XHR to send/commit data to the server, in callback do (if successful):
114410     e.record.commit();
114411 };
114412          * </code></pre>
114413          * @param {Ext.grid.plugin.Editing} editor
114414          * @param {Object} e An edit event (see above for description)
114415          */
114416         /**
114417          * @event validateedit
114418          * Fires after a cell is edited, but before the value is set in the record. Return false
114419          * to cancel the change. The edit event object has the following properties <br />
114420          * <ul style="padding:5px;padding-left:16px;">
114421          * <li>grid - The grid</li>
114422          * <li>record - The record being edited</li>
114423          * <li>field - The field name being edited</li>
114424          * <li>value - The value being set</li>
114425          * <li>originalValue - The original value for the field, before the edit.</li>
114426          * <li>row - The grid table row</li>
114427          * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.</li>
114428          * <li>rowIdx - The row index that is being edited</li>
114429          * <li>colIdx - The column index that is being edited</li>
114430          * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
114431          * </ul>
114432          * Usage example showing how to remove the red triangle (dirty record indicator) from some
114433          * records (not all).  By observing the grid's validateedit event, it can be cancelled if
114434          * the edit occurs on a targeted row (for example) and then setting the field's new value
114435          * in the Record directly:
114436          * <pre><code>
114437 grid.on('validateedit', function(e) {
114438   var myTargetRow = 6;
114439
114440   if (e.row == myTargetRow) {
114441     e.cancel = true;
114442     e.record.data[e.field] = e.value;
114443   }
114444 });
114445          * </code></pre>
114446          * @param {Ext.grid.plugin.Editing} editor
114447          * @param {Object} e An edit event (see above for description)
114448          */
114449         this.callParent(arguments);
114450         this.editors = Ext.create('Ext.util.MixedCollection', false, function(editor) {
114451             return editor.editorId;
114452         });
114453     },
114454
114455     /**
114456      * @private
114457      * AbstractComponent calls destroy on all its plugins at destroy time.
114458      */
114459     destroy: function() {
114460         var me = this;
114461         me.editors.each(Ext.destroy, Ext);
114462         me.editors.clear();
114463         me.callParent(arguments);
114464     },
114465     
114466     onBodyScroll: function() {
114467         var ed = this.getActiveEditor();
114468         if (ed && ed.field) {
114469             if (ed.field.triggerBlur) {
114470                 ed.field.triggerBlur();
114471             } else {
114472                 ed.field.blur();
114473             }
114474         }
114475     },
114476
114477     // private
114478     // Template method called from base class's initEvents
114479     initCancelTriggers: function() {
114480         var me   = this,
114481             grid = me.grid,
114482             view = grid.view;
114483             
114484         view.addElListener('mousewheel', me.cancelEdit, me);
114485         me.mon(view, 'bodyscroll', me.onBodyScroll, me);
114486         me.mon(grid, {
114487             columnresize: me.cancelEdit,
114488             columnmove: me.cancelEdit,
114489             scope: me
114490         });
114491     },
114492
114493     /**
114494      * Start editing the specified record, using the specified Column definition to define which field is being edited.
114495      * @param {Model} record The Store data record which backs the row to be edited.
114496      * @param {Model} columnHeader The Column object defining the column to be edited.
114497      * @override
114498      */
114499     startEdit: function(record, columnHeader) {
114500         var me = this,
114501             ed   = me.getEditor(record, columnHeader),
114502             value = record.get(columnHeader.dataIndex),
114503             context = me.getEditingContext(record, columnHeader);
114504
114505         record = context.record;
114506         columnHeader = context.column;
114507
114508         // Complete the edit now, before getting the editor's target
114509         // cell DOM element. Completing the edit causes a view refresh.
114510         me.completeEdit();
114511
114512         // See if the field is editable for the requested record
114513         if (columnHeader && !columnHeader.getEditor(record)) {
114514             return false;
114515         }
114516
114517         if (ed) {
114518             context.originalValue = context.value = value;
114519             if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
114520                 return false;
114521             }
114522
114523             me.context = context;
114524             me.setActiveEditor(ed);
114525             me.setActiveRecord(record);
114526             me.setActiveColumn(columnHeader);
114527
114528             // Defer, so we have some time between view scroll to sync up the editor
114529             Ext.defer(ed.startEdit, 15, ed, [me.getCell(record, columnHeader), value]);
114530         } else {
114531             // BrowserBug: WebKit & IE refuse to focus the element, rather
114532             // it will focus it and then immediately focus the body. This
114533             // temporary hack works for Webkit and IE6. IE7 and 8 are still
114534             // broken
114535             me.grid.getView().el.focus((Ext.isWebKit || Ext.isIE) ? 10 : false);
114536         }
114537     },
114538
114539     completeEdit: function() {
114540         var activeEd = this.getActiveEditor();
114541         if (activeEd) {
114542             activeEd.completeEdit();
114543         }
114544     },
114545
114546     // internal getters/setters
114547     setActiveEditor: function(ed) {
114548         this.activeEditor = ed;
114549     },
114550
114551     getActiveEditor: function() {
114552         return this.activeEditor;
114553     },
114554
114555     setActiveColumn: function(column) {
114556         this.activeColumn = column;
114557     },
114558
114559     getActiveColumn: function() {
114560         return this.activeColumn;
114561     },
114562
114563     setActiveRecord: function(record) {
114564         this.activeRecord = record;
114565     },
114566
114567     getActiveRecord: function() {
114568         return this.activeRecord;
114569     },
114570
114571     getEditor: function(record, column) {
114572         var me = this,
114573             editors = me.editors,
114574             editorId = column.itemId || column.id,
114575             editor = editors.getByKey(editorId);
114576
114577         if (editor) {
114578             return editor;
114579         } else {
114580             editor = column.getEditor(record);
114581             if (!editor) {
114582                 return false;
114583             }
114584
114585             // Allow them to specify a CellEditor in the Column
114586             if (!(editor instanceof Ext.grid.CellEditor)) {
114587                 editor = Ext.create('Ext.grid.CellEditor', {
114588                     editorId: editorId,
114589                     field: editor
114590                 });
114591             }
114592             editor.parentEl = me.grid.getEditorParent();
114593             // editor.parentEl should be set here.
114594             editor.on({
114595                 scope: me,
114596                 specialkey: me.onSpecialKey,
114597                 complete: me.onEditComplete,
114598                 canceledit: me.cancelEdit
114599             });
114600             editors.add(editor);
114601             return editor;
114602         }
114603     },
114604
114605     /**
114606      * Get the cell (td) for a particular record and column.
114607      * @param {Ext.data.Model} record
114608      * @param {Ext.grid.column.Colunm} column
114609      * @private
114610      */
114611     getCell: function(record, column) {
114612         return this.grid.getView().getCell(record, column);
114613     },
114614
114615     onSpecialKey: function(ed, field, e) {
114616         var grid = this.grid,
114617             sm;
114618         if (e.getKey() === e.TAB) {
114619             e.stopEvent();
114620             sm = grid.getSelectionModel();
114621             if (sm.onEditorTab) {
114622                 sm.onEditorTab(this, e);
114623             }
114624         }
114625     },
114626
114627     onEditComplete : function(ed, value, startValue) {
114628         var me = this,
114629             grid = me.grid,
114630             sm = grid.getSelectionModel(),
114631             activeColumn = me.getActiveColumn(),
114632             dataIndex;
114633
114634         if (activeColumn) {
114635             dataIndex = activeColumn.dataIndex;
114636
114637             me.setActiveEditor(null);
114638             me.setActiveColumn(null);
114639             me.setActiveRecord(null);
114640             delete sm.wasEditing;
114641     
114642             if (!me.validateEdit()) {
114643                 return;
114644             }
114645             // Only update the record if the new value is different than the
114646             // startValue, when the view refreshes its el will gain focus
114647             if (value !== startValue) {
114648                 me.context.record.set(dataIndex, value);
114649             // Restore focus back to the view's element.
114650             } else {
114651                 grid.getView().el.focus();
114652             }
114653             me.context.value = value;
114654             me.fireEvent('edit', me, me.context);
114655             
114656
114657         }
114658     },
114659
114660     /**
114661      * Cancel any active editing.
114662      */
114663     cancelEdit: function() {
114664         var me = this,
114665             activeEd = me.getActiveEditor(),
114666             viewEl = me.grid.getView().el;
114667
114668         me.setActiveEditor(null);
114669         me.setActiveColumn(null);
114670         me.setActiveRecord(null);
114671         if (activeEd) {
114672             activeEd.cancelEdit();
114673             viewEl.focus();
114674         }
114675     },
114676
114677     /**
114678      * Starts editing by position (row/column)
114679      * @param {Object} position A position with keys of row and column.
114680      */
114681     startEditByPosition: function(position) {
114682         var me = this,
114683             grid = me.grid,
114684             sm = grid.getSelectionModel(),
114685             editRecord = grid.store.getAt(position.row),
114686             editColumnHeader = grid.headerCt.getHeaderAtIndex(position.column);
114687
114688         if (sm.selectByPosition) {
114689             sm.selectByPosition(position);
114690         }
114691         me.startEdit(editRecord, editColumnHeader);
114692     }
114693 });
114694 /**
114695  * @class Ext.grid.plugin.DragDrop
114696  * <p>This plugin provides drag and/or drop functionality for a GridView.</p>
114697  * <p>It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a {@link Ext.grid.View GridView}
114698  * and loads the data object which is passed to a cooperating {@link Ext.dd.DragZone DragZone}'s methods with the following properties:<ul>
114699  * <li>copy : Boolean
114700  *  <div class="sub-desc">The value of the GridView's <code>copy</code> property, or <code>true</code> if the GridView was configured
114701  *  with <code>allowCopy: true</code> <u>and</u> the control key was pressed when the drag operation was begun.</div></li>
114702  * <li>view : GridView
114703  *  <div class="sub-desc">The source GridView from which the drag originated.</div></li>
114704  * <li>ddel : HtmlElement
114705  *  <div class="sub-desc">The drag proxy element which moves with the mouse</div></li>
114706  * <li>item : HtmlElement
114707  *  <div class="sub-desc">The GridView node upon which the mousedown event was registered.</div></li>
114708  * <li>records : Array
114709  *  <div class="sub-desc">An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.</div></li>
114710  * </ul></p>
114711  * <p>It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are members of the same
114712  * ddGroup which processes such data objects.</p>
114713  * <p>Adding this plugin to a view means that two new events may be fired from the client GridView, <code>{@link #event-beforedrop beforedrop}</code> and
114714  * <code>{@link #event-drop drop}</code></p>
114715  */
114716 Ext.define('Ext.grid.plugin.DragDrop', {
114717     extend: 'Ext.AbstractPlugin',
114718     alias: 'plugin.gridviewdragdrop',
114719
114720     uses: [
114721         'Ext.view.DragZone',
114722         'Ext.grid.ViewDropZone'
114723     ],
114724
114725     /**
114726      * @event beforedrop
114727      * <p><b>This event is fired through the GridView. Add listeners to the GridView object</b></p>
114728      * <p>Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the GridView.
114729      * @param {HtmlElement} node The GridView node <b>if any</b> over which the mouse was positioned.</p>
114730      * <p>Returning <code>false</code> to this event signals that the drop gesture was invalid, and if the drag proxy
114731      * will animate back to the point from which the drag began.</p>
114732      * <p>Returning <code>0</code> To this event signals that the data transfer operation should not take place, but
114733      * that the gesture was valid, and that the repair operation should not take place.</p>
114734      * <p>Any other return value continues with the data transfer operation.</p>
114735      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone DragZone}'s
114736      * {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:<ul>
114737      * <li>copy : Boolean
114738      *  <div class="sub-desc">The value of the GridView's <code>copy</code> property, or <code>true</code> if the GridView was configured
114739      *  with <code>allowCopy: true</code> and the control key was pressed when the drag operation was begun</div></li>
114740      * <li>view : GridView
114741      *  <div class="sub-desc">The source GridView from which the drag originated.</div></li>
114742      * <li>ddel : HtmlElement
114743      *  <div class="sub-desc">The drag proxy element which moves with the mouse</div></li>
114744      * <li>item : HtmlElement
114745      *  <div class="sub-desc">The GridView node upon which the mousedown event was registered.</div></li>
114746      * <li>records : Array
114747      *  <div class="sub-desc">An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.</div></li>
114748      * </ul>
114749      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
114750      * @param {String} dropPosition <code>"before"</code> or <code>"after"</code> depending on whether the mouse is above or below the midline of the node.
114751      * @param {Function} dropFunction <p>A function to call to complete the data transfer operation and either move or copy Model instances from the source
114752      * View's Store to the destination View's Store.</p>
114753      * <p>This is useful when you want to perform some kind of asynchronous processing before confirming
114754      * the drop, such as an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.</p>
114755      * <p>Return <code>0</code> from this event handler, and call the <code>dropFunction</code> at any time to perform the data transfer.</p>
114756      */
114757
114758     /**
114759      * @event drop
114760      * <b>This event is fired through the GridView. Add listeners to the GridView object</b>
114761      * Fired when a drop operation has been completed and the data has been moved or copied.
114762      * @param {HtmlElement} node The GridView node <b>if any</b> over which the mouse was positioned.
114763      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone DragZone}'s
114764      * {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:<ul>
114765      * <li>copy : Boolean
114766      *  <div class="sub-desc">The value of the GridView's <code>copy</code> property, or <code>true</code> if the GridView was configured
114767      *  with <code>allowCopy: true</code> and the control key was pressed when the drag operation was begun</div></li>
114768      * <li>view : GridView
114769      *  <div class="sub-desc">The source GridView from which the drag originated.</div></li>
114770      * <li>ddel : HtmlElement
114771      *  <div class="sub-desc">The drag proxy element which moves with the mouse</div></li>
114772      * <li>item : HtmlElement
114773      *  <div class="sub-desc">The GridView node upon which the mousedown event was registered.</div></li>
114774      * <li>records : Array
114775      *  <div class="sub-desc">An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.</div></li>
114776      * </ul>
114777      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
114778      * @param {String} dropPosition <code>"before"</code> or <code>"after"</code> depending on whether the mouse is above or below the midline of the node.
114779      */
114780
114781     dragText : '{0} selected row{1}',
114782
114783     /**
114784      * @cfg {String} ddGroup
114785      * A named drag drop group to which this object belongs.  If a group is specified, then both the DragZones and DropZone
114786      * used by this plugin will only interact with other drag drop objects in the same group (defaults to 'TreeDD').
114787      */
114788     ddGroup : "GridDD",
114789
114790     /**
114791      * @cfg {String} dragGroup
114792      * <p>The ddGroup to which the DragZone will belong.</p>
114793      * <p>This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other Drag/DropZones
114794      * which are members of the same ddGroup.</p>
114795      */
114796
114797     /**
114798      * @cfg {String} dropGroup
114799      * <p>The ddGroup to which the DropZone will belong.</p>
114800      * <p>This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other Drag/DropZones
114801      * which are members of the same ddGroup.</p>
114802      */
114803
114804     /**
114805      * @cfg {Boolean} enableDrop
114806      * <p>Defaults to <code>true</code></p>
114807      * <p>Set to <code>false</code> to disallow the View from accepting drop gestures</p>
114808      */
114809     enableDrop: true,
114810
114811     /**
114812      * @cfg {Boolean} enableDrag
114813      * <p>Defaults to <code>true</code></p>
114814      * <p>Set to <code>false</code> to disallow dragging items from the View </p>
114815      */
114816     enableDrag: true,
114817
114818     init : function(view) {
114819         view.on('render', this.onViewRender, this, {single: true});
114820     },
114821
114822     /**
114823      * @private
114824      * AbstractComponent calls destroy on all its plugins at destroy time.
114825      */
114826     destroy: function() {
114827         Ext.destroy(this.dragZone, this.dropZone);
114828     },
114829
114830     onViewRender : function(view) {
114831         var me = this;
114832
114833         if (me.enableDrag) {
114834             me.dragZone = Ext.create('Ext.view.DragZone', {
114835                 view: view,
114836                 ddGroup: me.dragGroup || me.ddGroup,
114837                 dragText: me.dragText
114838             });
114839         }
114840
114841         if (me.enableDrop) {
114842             me.dropZone = Ext.create('Ext.grid.ViewDropZone', {
114843                 view: view,
114844                 ddGroup: me.dropGroup || me.ddGroup
114845             });
114846         }
114847     }
114848 });
114849 /**
114850  * @class Ext.grid.plugin.HeaderReorderer
114851  * @extends Ext.util.Observable
114852  * @private
114853  */
114854 Ext.define('Ext.grid.plugin.HeaderReorderer', {
114855     extend: 'Ext.util.Observable',
114856     requires: ['Ext.grid.header.DragZone', 'Ext.grid.header.DropZone'],
114857     alias: 'plugin.gridheaderreorderer',
114858
114859     init: function(headerCt) {
114860         this.headerCt = headerCt;
114861         headerCt.on('render', this.onHeaderCtRender, this);
114862     },
114863
114864     /**
114865      * @private
114866      * AbstractComponent calls destroy on all its plugins at destroy time.
114867      */
114868     destroy: function() {
114869         Ext.destroy(this.dragZone, this.dropZone);
114870     },
114871
114872     onHeaderCtRender: function() {
114873         this.dragZone = Ext.create('Ext.grid.header.DragZone', this.headerCt);
114874         this.dropZone = Ext.create('Ext.grid.header.DropZone', this.headerCt);
114875         if (this.disabled) {
114876             this.dragZone.disable();
114877         }
114878     },
114879     
114880     enable: function() {
114881         this.disabled = false;
114882         if (this.dragZone) {
114883             this.dragZone.enable();
114884         }
114885     },
114886     
114887     disable: function() {
114888         this.disabled = true;
114889         if (this.dragZone) {
114890             this.dragZone.disable();
114891         }
114892     }
114893 });
114894 /**
114895  * @class Ext.grid.plugin.HeaderResizer
114896  * @extends Ext.util.Observable
114897  * 
114898  * Plugin to add header resizing functionality to a HeaderContainer.
114899  * Always resizing header to the left of the splitter you are resizing.
114900  * 
114901  * Todo: Consider RTL support, columns would always calculate to the right of
114902  *    the splitter instead of to the left.
114903  */
114904 Ext.define('Ext.grid.plugin.HeaderResizer', {
114905     extend: 'Ext.util.Observable',
114906     requires: ['Ext.dd.DragTracker', 'Ext.util.Region'],
114907     alias: 'plugin.gridheaderresizer',
114908     
114909     disabled: false,
114910
114911     /**
114912      * @cfg {Boolean} dynamic
114913      * Set to true to resize on the fly rather than using a proxy marker. Defaults to false.
114914      */
114915     configs: {
114916         dynamic: true
114917     },
114918
114919     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
114920
114921     minColWidth: 40,
114922     maxColWidth: 1000,
114923     wResizeCursor: 'col-resize',
114924     eResizeCursor: 'col-resize',
114925     // not using w and e resize bc we are only ever resizing one
114926     // column
114927     //wResizeCursor: Ext.isWebKit ? 'w-resize' : 'col-resize',
114928     //eResizeCursor: Ext.isWebKit ? 'e-resize' : 'col-resize',
114929
114930     init: function(headerCt) {
114931         this.headerCt = headerCt;
114932         headerCt.on('render', this.afterHeaderRender, this, {single: true});
114933     },
114934
114935     /**
114936      * @private
114937      * AbstractComponent calls destroy on all its plugins at destroy time.
114938      */
114939     destroy: function() {
114940         if (this.tracker) {
114941             this.tracker.destroy();
114942         }
114943     },
114944
114945     afterHeaderRender: function() {
114946         var headerCt = this.headerCt,
114947             el = headerCt.el;
114948
114949         headerCt.mon(el, 'mousemove', this.onHeaderCtMouseMove, this);
114950
114951         this.tracker = Ext.create('Ext.dd.DragTracker', {
114952             disabled: this.disabled,
114953             onBeforeStart: Ext.Function.bind(this.onBeforeStart, this),
114954             onStart: Ext.Function.bind(this.onStart, this),
114955             onDrag: Ext.Function.bind(this.onDrag, this),
114956             onEnd: Ext.Function.bind(this.onEnd, this),
114957             tolerance: 3,
114958             autoStart: 300,
114959             el: el
114960         });
114961     },
114962
114963     // As we mouse over individual headers, change the cursor to indicate
114964     // that resizing is available, and cache the resize target header for use
114965     // if/when they mousedown.
114966     onHeaderCtMouseMove: function(e, t) {
114967         if (this.headerCt.dragging) {
114968             if (this.activeHd) {
114969                 this.activeHd.el.dom.style.cursor = '';
114970                 delete this.activeHd;
114971             }
114972         } else {
114973             var headerEl = e.getTarget('.' + this.colHeaderCls, 3, true),
114974                 overHeader, resizeHeader;
114975
114976             if (headerEl){
114977                 overHeader = Ext.getCmp(headerEl.id);
114978
114979                 // On left edge, we are resizing the previous non-hidden, base level column.
114980                 if (overHeader.isOnLeftEdge(e)) {
114981                     resizeHeader = overHeader.previousNode('gridcolumn:not([hidden]):not([isGroupHeader])');
114982                 }
114983                 // Else, if on the right edge, we're resizing the column we are over
114984                 else if (overHeader.isOnRightEdge(e)) {
114985                     resizeHeader = overHeader;
114986                 }
114987                 // Between the edges: we are not resizing
114988                 else {
114989                     resizeHeader = null;
114990                 }
114991
114992                 // We *are* resizing
114993                 if (resizeHeader) {
114994                     // If we're attempting to resize a group header, that cannot be resized,
114995                     // so find its last base level column header; Group headers are sized
114996                     // by the size of their child headers.
114997                     if (resizeHeader.isGroupHeader) {
114998                         resizeHeader = resizeHeader.getVisibleGridColumns();
114999                         resizeHeader = resizeHeader[resizeHeader.length - 1];
115000                     }
115001
115002                     if (resizeHeader && !resizeHeader.fixed) {
115003                         this.activeHd = resizeHeader;
115004                         overHeader.el.dom.style.cursor = this.eResizeCursor;
115005                     }
115006                 // reset
115007                 } else {
115008                     overHeader.el.dom.style.cursor = '';
115009                     delete this.activeHd;
115010                 }
115011             }
115012         }
115013     },
115014
115015     // only start when there is an activeHd
115016     onBeforeStart : function(e){
115017         var t = e.getTarget();
115018         // cache the activeHd because it will be cleared.
115019         this.dragHd = this.activeHd;
115020
115021         if (!!this.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger') && !this.headerCt.dragging) {
115022             //this.headerCt.dragging = true;
115023             this.tracker.constrainTo = this.getConstrainRegion();
115024             return true;
115025         } else {
115026             this.headerCt.dragging = false;
115027             return false;
115028         }
115029     },
115030
115031     // get the region to constrain to, takes into account max and min col widths
115032     getConstrainRegion: function() {
115033         var dragHdEl = this.dragHd.el,
115034             region   = Ext.util.Region.getRegion(dragHdEl);
115035
115036         return region.adjust(
115037             0,
115038             this.maxColWidth - dragHdEl.getWidth(),
115039             0,
115040             this.minColWidth
115041         );
115042     },
115043
115044     // initialize the left and right hand side markers around
115045     // the header that we are resizing
115046     onStart: function(e){
115047         var me       = this,
115048             dragHd   = me.dragHd,
115049             dragHdEl = dragHd.el,
115050             width    = dragHdEl.getWidth(),
115051             headerCt = me.headerCt,
115052             t        = e.getTarget();
115053
115054         if (me.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger')) {
115055             headerCt.dragging = true;
115056         }
115057
115058         me.origWidth = width;
115059
115060         // setup marker proxies
115061         if (!me.dynamic) {
115062             var xy           = dragHdEl.getXY(),
115063                 gridSection  = headerCt.up('[scrollerOwner]'),
115064                 dragHct      = me.dragHd.up(':not([isGroupHeader])'),
115065                 firstSection = dragHct.up(),
115066                 lhsMarker    = gridSection.getLhsMarker(),
115067                 rhsMarker    = gridSection.getRhsMarker(),
115068                 el           = rhsMarker.parent(),
115069                 offsetLeft   = el.getLeft(true),
115070                 offsetTop    = el.getTop(true),
115071                 topLeft      = el.translatePoints(xy),
115072                 markerHeight = firstSection.body.getHeight() + headerCt.getHeight(),
115073                 top = topLeft.top - offsetTop;
115074
115075             lhsMarker.setTop(top);
115076             rhsMarker.setTop(top);
115077             lhsMarker.setHeight(markerHeight);
115078             rhsMarker.setHeight(markerHeight);
115079             lhsMarker.setLeft(topLeft.left - offsetLeft);
115080             rhsMarker.setLeft(topLeft.left + width - offsetLeft);
115081         }
115082     },
115083
115084     // synchronize the rhsMarker with the mouse movement
115085     onDrag: function(e){
115086         if (!this.dynamic) {
115087             var xy          = this.tracker.getXY('point'),
115088                 gridSection = this.headerCt.up('[scrollerOwner]'),
115089                 rhsMarker   = gridSection.getRhsMarker(),
115090                 el          = rhsMarker.parent(),
115091                 topLeft     = el.translatePoints(xy),
115092                 offsetLeft  = el.getLeft(true);
115093
115094             rhsMarker.setLeft(topLeft.left - offsetLeft);
115095         // Resize as user interacts
115096         } else {
115097             this.doResize();
115098         }
115099     },
115100
115101     onEnd: function(e){
115102         this.headerCt.dragging = false;
115103         if (this.dragHd) {
115104             if (!this.dynamic) {
115105                 var dragHd      = this.dragHd,
115106                     gridSection = this.headerCt.up('[scrollerOwner]'),
115107                     lhsMarker   = gridSection.getLhsMarker(),
115108                     rhsMarker   = gridSection.getRhsMarker(),
115109                     currWidth   = dragHd.getWidth(),
115110                     offset      = this.tracker.getOffset('point'),
115111                     offscreen   = -9999;
115112
115113                 // hide markers
115114                 lhsMarker.setLeft(offscreen);
115115                 rhsMarker.setLeft(offscreen);
115116             }
115117             this.doResize();
115118         }
115119     },
115120
115121     doResize: function() {
115122         if (this.dragHd) {
115123             var dragHd = this.dragHd,
115124                 nextHd,
115125                 offset = this.tracker.getOffset('point');
115126
115127             // resize the dragHd
115128             if (dragHd.flex) {
115129                 delete dragHd.flex;
115130             }
115131
115132             // If HeaderContainer is configured forceFit, inhibit upstream layout notification, so that
115133             // we can also shrink the following Header by an equal amount, and *then* inform the upstream layout.
115134             if (this.headerCt.forceFit) {
115135                 nextHd = dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
115136                 if (nextHd) {
115137                     this.headerCt.componentLayout.layoutBusy = true;
115138                 }
115139             }
115140
115141             // Non-flexed Headers may never be squeezed in the event of a shortfall so
115142             // always set the minWidth to their current width.
115143             dragHd.minWidth = this.origWidth + offset[0];
115144             dragHd.setWidth(dragHd.minWidth);
115145
115146             // In the case of forceFit, change the following Header width.
115147             // Then apply the two width changes by laying out the owning HeaderContainer
115148             if (nextHd) {
115149                 delete nextHd.flex;
115150                 nextHd.setWidth(nextHd.getWidth() - offset[0]);
115151                 this.headerCt.componentLayout.layoutBusy = false;
115152                 this.headerCt.doComponentLayout();
115153             }
115154         }
115155     },
115156     
115157     disable: function() {
115158         this.disabled = true;
115159         if (this.tracker) {
115160             this.tracker.disable();
115161         }
115162     },
115163     
115164     enable: function() {
115165         this.disabled = false;
115166         if (this.tracker) {
115167             this.tracker.enable();
115168         }
115169     }
115170 });
115171 /**
115172  * @class Ext.grid.plugin.RowEditing
115173  * @extends Ext.grid.plugin.Editing
115174  * 
115175  * The Ext.grid.plugin.RowEditing plugin injects editing at a row level for a Grid. When editing begins,
115176  * a small floating dialog will be shown for the appropriate row. Each editable column will show a field
115177  * for editing. There is a button to save or cancel all changes for the edit.
115178  * 
115179  * The field that will be used for the editor is defined at the
115180  * {@link Ext.grid.column.Column#field field}. The editor can be a field instance or a field configuration.
115181  * If an editor is not specified for a particular column then that column won't be editable and the value of
115182  * the column will be displayed.
115183  *
115184  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
115185  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
115186  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
115187  * 
115188  * {@img Ext.grid.plugin.RowEditing/Ext.grid.plugin.RowEditing.png Ext.grid.plugin.RowEditing plugin}
115189  *
115190  * ## Example Usage
115191  *
115192  *     Ext.create('Ext.data.Store', {
115193  *         storeId:'simpsonsStore',
115194  *         fields:['name', 'email', 'phone'],
115195  *         data:{'items':[
115196  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
115197  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
115198  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},                        
115199  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}            
115200  *         ]},
115201  *         proxy: {
115202  *             type: 'memory',
115203  *             reader: {
115204  *                 type: 'json',
115205  *                 root: 'items'
115206  *             }
115207  *         }
115208  *     });
115209  *     
115210  *     Ext.create('Ext.grid.Panel', {
115211  *         title: 'Simpsons',
115212  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
115213  *         columns: [
115214  *             {header: 'Name',  dataIndex: 'name', field: 'textfield'},
115215  *             {header: 'Email', dataIndex: 'email', flex:1, 
115216  *                 editor: {
115217  *                     xtype:'textfield',
115218  *                     allowBlank:false
115219  *                 }
115220  *             },
115221  *             {header: 'Phone', dataIndex: 'phone'}
115222  *         ],
115223  *         selType: 'rowmodel',
115224  *         plugins: [
115225  *             Ext.create('Ext.grid.plugin.RowEditing', {
115226  *                 clicksToEdit: 1
115227  *             })
115228  *         ],
115229  *         height: 200,
115230  *         width: 400,
115231  *         renderTo: Ext.getBody()
115232  *     });
115233  */
115234 Ext.define('Ext.grid.plugin.RowEditing', {
115235     extend: 'Ext.grid.plugin.Editing',
115236     alias: 'plugin.rowediting',
115237
115238     requires: [
115239         'Ext.grid.RowEditor'
115240     ],
115241
115242     editStyle: 'row',
115243
115244     /**
115245      * @cfg {Boolean} autoCancel
115246      * `true` to automatically cancel any pending changes when the row editor begins editing a new row.
115247      * `false` to force the user to explicitly cancel the pending changes. Defaults to `true`.
115248      * @markdown
115249      */
115250     autoCancel: true,
115251
115252     /**
115253      * @cfg {Number} clicksToMoveEditor
115254      * The number of clicks to move the row editor to a new row while it is visible and actively editing another row.
115255      * This will default to the same value as {@link Ext.grid.plugin.Editing#clicksToEdit clicksToEdit}.
115256      * @markdown
115257      */
115258
115259     /**
115260      * @cfg {Boolean} errorSummary
115261      * `true` to show a {@link Ext.tip.ToolTip tooltip} that summarizes all validation errors present
115262      * in the row editor. Set to `false` to prevent the tooltip from showing. Defaults to `true`.
115263      * @markdown
115264      */
115265     errorSummary: true,
115266
115267     /**
115268      * @event beforeedit
115269      * Fires before row editing is triggered. The edit event object has the following properties <br />
115270      * <ul style="padding:5px;padding-left:16px;">
115271      * <li>grid - The grid this editor is on</li>
115272      * <li>view - The grid view</li>
115273      * <li>store - The grid store</li>
115274      * <li>record - The record being edited</li>
115275      * <li>row - The grid table row</li>
115276      * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit</li>
115277      * <li>rowIdx - The row index that is being edited</li>
115278      * <li>colIdx - The column index that initiated the edit</li>
115279      * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
115280      * </ul>
115281      * @param {Ext.grid.plugin.Editing} editor
115282      * @param {Object} e An edit event (see above for description)
115283      */
115284     /**
115285      * @event edit
115286      * Fires after a row is edited. The edit event object has the following properties <br />
115287      * <ul style="padding:5px;padding-left:16px;">
115288      * <li>grid - The grid this editor is on</li>
115289      * <li>view - The grid view</li>
115290      * <li>store - The grid store</li>
115291      * <li>record - The record being edited</li>
115292      * <li>row - The grid table row</li>
115293      * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit</li>
115294      * <li>rowIdx - The row index that is being edited</li>
115295      * <li>colIdx - The column index that initiated the edit</li>
115296      * </ul>
115297      *
115298      * <pre><code>
115299 grid.on('edit', onEdit, this);
115300
115301 function onEdit(e) {
115302     // execute an XHR to send/commit data to the server, in callback do (if successful):
115303     e.record.commit();
115304 };
115305      * </code></pre>
115306      * @param {Ext.grid.plugin.Editing} editor
115307      * @param {Object} e An edit event (see above for description)
115308      */
115309     /**
115310      * @event validateedit
115311      * Fires after a cell is edited, but before the value is set in the record. Return false
115312      * to cancel the change. The edit event object has the following properties <br />
115313      * <ul style="padding:5px;padding-left:16px;">
115314      * <li>grid - The grid this editor is on</li>
115315      * <li>view - The grid view</li>
115316      * <li>store - The grid store</li>
115317      * <li>record - The record being edited</li>
115318      * <li>row - The grid table row</li>
115319      * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit</li>
115320      * <li>rowIdx - The row index that is being edited</li>
115321      * <li>colIdx - The column index that initiated the edit</li>
115322      * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
115323      * </ul>
115324      * Usage example showing how to remove the red triangle (dirty record indicator) from some
115325      * records (not all).  By observing the grid's validateedit event, it can be cancelled if
115326      * the edit occurs on a targeted row (for example) and then setting the field's new value
115327      * in the Record directly:
115328      * <pre><code>
115329 grid.on('validateedit', function(e) {
115330   var myTargetRow = 6;
115331
115332   if (e.rowIdx == myTargetRow) {
115333     e.cancel = true;
115334     e.record.data[e.field] = e.value;
115335   }
115336 });
115337      * </code></pre>
115338      * @param {Ext.grid.plugin.Editing} editor
115339      * @param {Object} e An edit event (see above for description)
115340      */
115341
115342     constructor: function() {
115343         var me = this;
115344         me.callParent(arguments);
115345
115346         if (!me.clicksToMoveEditor) {
115347             me.clicksToMoveEditor = me.clicksToEdit;
115348         }
115349
115350         me.autoCancel = !!me.autoCancel;
115351     },
115352
115353     /**
115354      * @private
115355      * AbstractComponent calls destroy on all its plugins at destroy time.
115356      */
115357     destroy: function() {
115358         var me = this;
115359         Ext.destroy(me.editor);
115360         me.callParent(arguments);
115361     },
115362
115363     /**
115364      * Start editing the specified record, using the specified Column definition to define which field is being edited.
115365      * @param {Model} record The Store data record which backs the row to be edited.
115366      * @param {Model} columnHeader The Column object defining the column to be edited.
115367      * @override
115368      */
115369     startEdit: function(record, columnHeader) {
115370         var me = this,
115371             editor = me.getEditor();
115372
115373         if (me.callParent(arguments) === false) {
115374             return false;
115375         }
115376
115377         // Fire off our editor
115378         if (editor.beforeEdit() !== false) {
115379             editor.startEdit(me.context.record, me.context.column);
115380         }
115381     },
115382
115383     // private
115384     cancelEdit: function() {
115385         var me = this;
115386
115387         if (me.editing) {
115388             me.getEditor().cancelEdit();
115389             me.callParent(arguments);
115390         }
115391     },
115392
115393     // private
115394     completeEdit: function() {
115395         var me = this;
115396
115397         if (me.editing && me.validateEdit()) {
115398             me.editing = false;
115399             me.fireEvent('edit', me.context);
115400         }
115401     },
115402
115403     // private
115404     validateEdit: function() {
115405         var me = this;
115406         return me.callParent(arguments) && me.getEditor().completeEdit();
115407     },
115408
115409     // private
115410     getEditor: function() {
115411         var me = this;
115412
115413         if (!me.editor) {
115414             me.editor = me.initEditor();
115415         }
115416         return me.editor;
115417     },
115418
115419     // private
115420     initEditor: function() {
115421         var me = this,
115422             grid = me.grid,
115423             view = me.view,
115424             headerCt = grid.headerCt;
115425
115426         return Ext.create('Ext.grid.RowEditor', {
115427             autoCancel: me.autoCancel,
115428             errorSummary: me.errorSummary,
115429             fields: headerCt.getGridColumns(),
115430             hidden: true,
115431
115432             // keep a reference..
115433             editingPlugin: me,
115434             renderTo: view.el
115435         });
115436     },
115437
115438     // private
115439     initEditTriggers: function() {
115440         var me = this,
115441             grid = me.grid,
115442             view = me.view,
115443             headerCt = grid.headerCt,
115444             moveEditorEvent = me.clicksToMoveEditor === 1 ? 'click' : 'dblclick';
115445
115446         me.callParent(arguments);
115447
115448         if (me.clicksToMoveEditor !== me.clicksToEdit) {
115449             me.mon(view, 'cell' + moveEditorEvent, me.moveEditorByClick, me);
115450         }
115451
115452         view.on('render', function() {
115453             // Column events
115454             me.mon(headerCt, {
115455                 add: me.onColumnAdd,
115456                 remove: me.onColumnRemove,
115457                 columnresize: me.onColumnResize,
115458                 columnhide: me.onColumnHide,
115459                 columnshow: me.onColumnShow,
115460                 columnmove: me.onColumnMove,
115461                 scope: me
115462             });
115463         }, me, { single: true });
115464     },
115465
115466     startEditByClick: function() {
115467         var me = this;
115468         if (!me.editing || me.clicksToMoveEditor === me.clicksToEdit) {
115469             me.callParent(arguments);
115470         }
115471     },
115472
115473     moveEditorByClick: function() {
115474         var me = this;
115475         if (me.editing) {
115476             me.superclass.startEditByClick.apply(me, arguments);
115477         }
115478     },
115479
115480     // private
115481     onColumnAdd: function(ct, column) {
115482         if (column.isHeader) {
115483             var me = this,
115484                 editor;
115485             
115486             me.initFieldAccessors(column);
115487             editor = me.getEditor();
115488             
115489             if (editor && editor.onColumnAdd) {
115490                 editor.onColumnAdd(column);
115491             }
115492         }
115493     },
115494
115495     // private
115496     onColumnRemove: function(ct, column) {
115497         if (column.isHeader) {
115498             var me = this,
115499                 editor = me.getEditor();
115500     
115501             if (editor && editor.onColumnRemove) {
115502                 editor.onColumnRemove(column);
115503             }
115504             me.removeFieldAccessors(column);  
115505         }
115506     },
115507
115508     // private
115509     onColumnResize: function(ct, column, width) {
115510         if (column.isHeader) {
115511             var me = this,
115512                 editor = me.getEditor();
115513     
115514             if (editor && editor.onColumnResize) {
115515                 editor.onColumnResize(column, width);
115516             }
115517         }
115518     },
115519
115520     // private
115521     onColumnHide: function(ct, column) {
115522         // no isHeader check here since its already a columnhide event.
115523         var me = this,
115524             editor = me.getEditor();
115525
115526         if (editor && editor.onColumnHide) {
115527             editor.onColumnHide(column);
115528         }
115529     },
115530
115531     // private
115532     onColumnShow: function(ct, column) {
115533         // no isHeader check here since its already a columnshow event.
115534         var me = this,
115535             editor = me.getEditor();
115536
115537         if (editor && editor.onColumnShow) {
115538             editor.onColumnShow(column);
115539         }
115540     },
115541
115542     // private
115543     onColumnMove: function(ct, column, fromIdx, toIdx) {
115544         // no isHeader check here since its already a columnmove event.
115545         var me = this,
115546             editor = me.getEditor();
115547
115548         if (editor && editor.onColumnMove) {
115549             editor.onColumnMove(column, fromIdx, toIdx);
115550         }
115551     },
115552
115553     // private
115554     setColumnField: function(column, field) {
115555         var me = this;
115556         me.callParent(arguments);
115557         me.getEditor().setField(column.field, column);
115558     }
115559 });
115560 /**
115561  * @class Ext.grid.property.Grid
115562  * @extends Ext.grid.Panel
115563  * A specialized grid implementation intended to mimic the traditional property grid as typically seen in
115564  * development IDEs.  Each row in the grid represents a property of some object, and the data is stored
115565  * as a set of name/value pairs in {@link Ext.grid.property.Property Properties}.  Example usage:
115566  * <pre><code>
115567 var grid = new Ext.grid.property.Grid({
115568     title: 'Properties Grid',
115569     width: 300,
115570     renderTo: 'grid-ct',
115571     source: {
115572         "(name)": "My Object",
115573         "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),
115574         "Available": false,
115575         "Version": .01,
115576         "Description": "A test object"
115577     }
115578 });
115579 </code></pre>
115580  * @constructor
115581  * @param {Object} config The grid config object
115582  * @xtype propertygrid
115583  */
115584 Ext.define('Ext.grid.property.Grid', {
115585
115586     extend: 'Ext.grid.Panel',
115587     
115588     alias: 'widget.propertygrid',
115589
115590     alternateClassName: 'Ext.grid.PropertyGrid',
115591
115592     uses: [
115593        'Ext.grid.plugin.CellEditing',
115594        'Ext.grid.property.Store',
115595        'Ext.grid.property.HeaderContainer',
115596        'Ext.XTemplate',
115597        'Ext.grid.CellEditor',
115598        'Ext.form.field.Date',
115599        'Ext.form.field.Text',
115600        'Ext.form.field.Number'
115601     ],
115602
115603    /**
115604     * @cfg {Object} propertyNames An object containing custom property name/display name pairs.
115605     * If specified, the display name will be shown in the name column instead of the property name.
115606     */
115607
115608     /**
115609     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
115610     */
115611
115612     /**
115613     * @cfg {Object} customEditors An object containing name/value pairs of custom editor type definitions that allow
115614     * the grid to support additional types of editable fields.  By default, the grid supports strongly-typed editing
115615     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
115616     * associated with a custom input control by specifying a custom editor.  The name of the editor
115617     * type should correspond with the name of the property that will use the editor.  Example usage:
115618     * <pre><code>
115619 var grid = new Ext.grid.property.Grid({
115620
115621     // Custom editors for certain property names
115622     customEditors: {
115623         evtStart: Ext.create('Ext.form.TimeField' {selectOnFocus:true})
115624     },
115625
115626     // Displayed name for property names in the source
115627     propertyNames: {
115628         evtStart: 'Start Time'
115629     },
115630
115631     // Data object containing properties to edit
115632     source: {
115633         evtStart: '10:00 AM'
115634     }
115635 });
115636 </code></pre>
115637     */
115638
115639     /**
115640     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
115641     */
115642
115643     /**
115644     * @cfg {Object} customRenderers An object containing name/value pairs of custom renderer type definitions that allow
115645     * the grid to support custom rendering of fields.  By default, the grid supports strongly-typed rendering
115646     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
115647     * associated with the type of the value.  The name of the renderer type should correspond with the name of the property
115648     * that it will render.  Example usage:
115649     * <pre><code>
115650 var grid = Ext.create('Ext.grid.property.Grid', {
115651     customRenderers: {
115652         Available: function(v){
115653             if (v) {
115654                 return '<span style="color: green;">Yes</span>';
115655             } else {
115656                 return '<span style="color: red;">No</span>';
115657             }
115658         }
115659     },
115660     source: {
115661         Available: true
115662     }
115663 });
115664 </code></pre>
115665     */
115666
115667     /**
115668      * @cfg {String} valueField
115669      * Optional. The name of the field from the property store to use as the value field name. Defaults to <code>'value'</code>
115670      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
115671      */
115672     valueField: 'value',
115673
115674     /**
115675      * @cfg {String} nameField
115676      * Optional. The name of the field from the property store to use as the property field name. Defaults to <code>'name'</code>
115677      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
115678      */
115679     nameField: 'name',
115680
115681     // private config overrides
115682     enableColumnMove: false,
115683     columnLines: true,
115684     stripeRows: false,
115685     trackMouseOver: false,
115686     clicksToEdit: 1,
115687     enableHdMenu: false,
115688
115689     // private
115690     initComponent : function(){
115691         var me = this;
115692
115693         me.addCls(Ext.baseCSSPrefix + 'property-grid');
115694         me.plugins = me.plugins || [];
115695
115696         // Enable cell editing. Inject a custom startEdit which always edits column 1 regardless of which column was clicked.
115697         me.plugins.push(Ext.create('Ext.grid.plugin.CellEditing', {
115698             clicksToEdit: me.clicksToEdit,
115699
115700             // Inject a startEdit which always edits the value column
115701             startEdit: function(record, column) {
115702                 // Maintainer: Do not change this 'this' to 'me'! It is the CellEditing object's own scope.
115703                 Ext.grid.plugin.CellEditing.prototype.startEdit.call(this, record, me.headerCt.child('#' + me.valueField));
115704             }
115705         }));
115706
115707         me.selModel = {
115708             selType: 'cellmodel',
115709             onCellSelect: function(position) {
115710                 if (position.column != 1) {
115711                     position.column = 1;
115712                     Ext.selection.CellModel.prototype.onCellSelect.call(this, position);
115713                 }
115714             }
115715         };
115716         me.customRenderers = me.customRenderers || {};
115717         me.customEditors = me.customEditors || {};
115718
115719         // Create a property.Store from the source object unless configured with a store
115720         if (!me.store) {
115721             me.propStore = me.store = Ext.create('Ext.grid.property.Store', me, me.source);
115722         }
115723
115724         me.store.sort('name', 'ASC');
115725         me.columns = Ext.create('Ext.grid.property.HeaderContainer', me, me.store);
115726
115727         me.addEvents(
115728             /**
115729              * @event beforepropertychange
115730              * Fires before a property value changes.  Handlers can return false to cancel the property change
115731              * (this will internally call {@link Ext.data.Record#reject} on the property's record).
115732              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
115733              * as the {@link #source} config property).
115734              * @param {String} recordId The record's id in the data store
115735              * @param {Mixed} value The current edited property value
115736              * @param {Mixed} oldValue The original property value prior to editing
115737              */
115738             'beforepropertychange',
115739             /**
115740              * @event propertychange
115741              * Fires after a property value has changed.
115742              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
115743              * as the {@link #source} config property).
115744              * @param {String} recordId The record's id in the data store
115745              * @param {Mixed} value The current edited property value
115746              * @param {Mixed} oldValue The original property value prior to editing
115747              */
115748             'propertychange'
115749         );
115750         me.callParent();
115751
115752         // Inject a custom implementation of walkCells which only goes up or down
115753         me.getView().walkCells = this.walkCells;
115754
115755         // Set up our default editor set for the 4 atomic data types
115756         me.editors = {
115757             'date'    : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Date',   {selectOnFocus: true})}),
115758             'string'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Text',   {selectOnFocus: true})}),
115759             'number'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Number', {selectOnFocus: true})}),
115760             'boolean' : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.ComboBox', {
115761                 editable: false,
115762                 store: [[ true, me.headerCt.trueText ], [false, me.headerCt.falseText ]]
115763             })})
115764         };
115765
115766         // Track changes to the data so we can fire our events.
115767         this.store.on('update', me.onUpdate, me);
115768     },
115769
115770     // private
115771     onUpdate : function(store, record, operation) {
115772         var me = this,
115773             v, oldValue;
115774
115775         if (operation == Ext.data.Model.EDIT) {
115776             v = record.get(me.valueField);
115777             oldValue = record.modified.value;
115778             if (me.fireEvent('beforepropertychange', me.source, record.getId(), v, oldValue) !== false) {
115779                 if (me.source) {
115780                     me.source[record.getId()] = v;
115781                 }
115782                 record.commit();
115783                 me.fireEvent('propertychange', me.source, record.getId(), v, oldValue);
115784             } else {
115785                 record.reject();
115786             }
115787         }
115788     },
115789
115790     // Custom implementation of walkCells which only goes up and down.
115791     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
115792         if (direction == 'left') {
115793             direction = 'up';
115794         } else if (direction == 'right') {
115795             direction = 'down';
115796         }
115797         pos = Ext.view.Table.prototype.walkCells.call(this, pos, direction, e, preventWrap, verifierFn, scope);
115798         if (!pos.column) {
115799             pos.column = 1;
115800         }
115801         return pos;
115802     },
115803
115804     // private
115805     // returns the correct editor type for the property type, or a custom one keyed by the property name
115806     getCellEditor : function(record, column) {
115807         var me = this,
115808             propName = record.get(me.nameField), 
115809             val = record.get(me.valueField),
115810             editor = me.customEditors[propName];
115811
115812         // A custom editor was found. If not already wrapped with a CellEditor, wrap it, and stash it back
115813         // If it's not even a Field, just a config object, instantiate it before wrapping it.
115814         if (editor) {
115815             if (!(editor instanceof Ext.grid.CellEditor)) {
115816                 if (!(editor instanceof Ext.form.field.Base)) {
115817                     editor = Ext.ComponentManager.create(editor, 'textfield');
115818                 }
115819                 editor = me.customEditors[propName] = Ext.create('Ext.grid.CellEditor', { field: editor });
115820             }
115821         } else if (Ext.isDate(val)) {
115822             editor = me.editors.date;
115823         } else if (Ext.isNumber(val)) {
115824             editor = me.editors.number;
115825         } else if (Ext.isBoolean(val)) {
115826             editor = me.editors['boolean'];
115827         } else {
115828             editor = me.editors.string;
115829         }
115830
115831         // Give the editor a unique ID because the CellEditing plugin caches them
115832         editor.editorId = propName;
115833         return editor;
115834     },
115835
115836     beforeDestroy: function() {
115837         var me = this;
115838         me.callParent();
115839         me.destroyEditors(me.editors);
115840         me.destroyEditors(me.customEditors);
115841         delete me.source;
115842     },
115843
115844     destroyEditors: function (editors) {
115845         for (var ed in editors) {
115846             if (editors.hasOwnProperty(ed)) {
115847                 Ext.destroy(editors[ed]);
115848             }
115849         }
115850     },
115851
115852     /**
115853      * Sets the source data object containing the property data.  The data object can contain one or more name/value
115854      * pairs representing all of the properties of an object to display in the grid, and this data will automatically
115855      * be loaded into the grid's {@link #store}.  The values should be supplied in the proper data type if needed,
115856      * otherwise string type will be assumed.  If the grid already contains data, this method will replace any
115857      * existing data.  See also the {@link #source} config value.  Example usage:
115858      * <pre><code>
115859 grid.setSource({
115860     "(name)": "My Object",
115861     "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),  // date type
115862     "Available": false,  // boolean type
115863     "Version": .01,      // decimal type
115864     "Description": "A test object"
115865 });
115866 </code></pre>
115867      * @param {Object} source The data object
115868      */
115869     setSource: function(source) {
115870         this.source = source;
115871         this.propStore.setSource(source);
115872     },
115873
115874     /**
115875      * Gets the source data object containing the property data.  See {@link #setSource} for details regarding the
115876      * format of the data object.
115877      * @return {Object} The data object
115878      */
115879     getSource: function() {
115880         return this.propStore.getSource();
115881     },
115882
115883     /**
115884      * Sets the value of a property.
115885      * @param {String} prop The name of the property to set
115886      * @param {Mixed} value The value to test
115887      * @param {Boolean} create (Optional) True to create the property if it doesn't already exist. Defaults to <tt>false</tt>.
115888      */
115889     setProperty: function(prop, value, create) {
115890         this.propStore.setValue(prop, value, create);
115891     },
115892
115893     /**
115894      * Removes a property from the grid.
115895      * @param {String} prop The name of the property to remove
115896      */
115897     removeProperty: function(prop) {
115898         this.propStore.remove(prop);
115899     }
115900
115901     /**
115902      * @cfg store
115903      * @hide
115904      */
115905     /**
115906      * @cfg colModel
115907      * @hide
115908      */
115909     /**
115910      * @cfg cm
115911      * @hide
115912      */
115913     /**
115914      * @cfg columns
115915      * @hide
115916      */
115917 });
115918 /**
115919  * @class Ext.grid.property.HeaderContainer
115920  * @extends Ext.grid.header.Container
115921  * A custom HeaderContainer for the {@link Ext.grid.property.Grid}.  Generally it should not need to be used directly.
115922  * @constructor
115923  * @param {Ext.grid.property.Grid} grid The grid this store will be bound to
115924  * @param {Object} source The source data config object
115925  */
115926 Ext.define('Ext.grid.property.HeaderContainer', {
115927
115928     extend: 'Ext.grid.header.Container',
115929
115930     alternateClassName: 'Ext.grid.PropertyColumnModel',
115931
115932     // private - strings used for locale support
115933     nameText : 'Name',
115934     valueText : 'Value',
115935     dateFormat : 'm/j/Y',
115936     trueText: 'true',
115937     falseText: 'false',
115938
115939     // private
115940     nameColumnCls: Ext.baseCSSPrefix + 'grid-property-name',
115941     
115942     constructor : function(grid, store) {
115943
115944         this.grid = grid;
115945         this.store = store;
115946         this.callParent([{
115947             items: [{
115948                 header: this.nameText,
115949                 width: 115,
115950                 sortable: true,
115951                 dataIndex: grid.nameField,
115952                 renderer: Ext.Function.bind(this.renderProp, this),
115953                 itemId: grid.nameField,
115954                 menuDisabled :true,
115955                 tdCls: this.nameColumnCls
115956             }, {
115957                 header: this.valueText,
115958                 renderer: Ext.Function.bind(this.renderCell, this),
115959                 getEditor: function(record) {
115960                     return grid.getCellEditor(record, this);
115961                 },
115962                 flex: 1,
115963                 fixed: true,
115964                 dataIndex: grid.valueField,
115965                 itemId: grid.valueField,
115966                 menuDisabled: true
115967             }]
115968         }]);
115969     },
115970
115971     // private
115972     // Render a property name cell
115973     renderProp : function(v) {
115974         return this.getPropertyName(v);
115975     },
115976
115977     // private
115978     // Render a property value cell
115979     renderCell : function(val, meta, rec) {
115980         var me = this,
115981             renderer = this.grid.customRenderers[rec.get(me.grid.nameField)],
115982             result = val;
115983
115984         if (renderer) {
115985             return renderer.apply(this, arguments);
115986         }
115987         if (Ext.isDate(val)) {
115988             result = this.renderDate(val);
115989         } else if (Ext.isBoolean(val)) {
115990             result = this.renderBool(val);
115991         }
115992         return Ext.util.Format.htmlEncode(result);
115993     },
115994
115995     // private
115996     renderDate : Ext.util.Format.date,
115997
115998     // private
115999     renderBool : function(bVal) {
116000         return this[bVal ? 'trueText' : 'falseText'];
116001     },
116002
116003     // private
116004     // Renders custom property names instead of raw names if defined in the Grid
116005     getPropertyName : function(name) {
116006         var pn = this.grid.propertyNames;
116007         return pn && pn[name] ? pn[name] : name;
116008     }
116009 });
116010 /**
116011  * @class Ext.grid.property.Property
116012  * A specific {@link Ext.data.Model} type that represents a name/value pair and is made to work with the
116013  * {@link Ext.grid.property.Grid}.  Typically, Properties do not need to be created directly as they can be
116014  * created implicitly by simply using the appropriate data configs either via the {@link Ext.grid.property.Grid#source}
116015  * config property or by calling {@link Ext.grid.property.Grid#setSource}.  However, if the need arises, these records
116016  * can also be created explicitly as shown below.  Example usage:
116017  * <pre><code>
116018 var rec = new Ext.grid.property.Property({
116019     name: 'birthday',
116020     value: Ext.Date.parse('17/06/1962', 'd/m/Y')
116021 });
116022 // Add record to an already populated grid
116023 grid.store.addSorted(rec);
116024 </code></pre>
116025  * @constructor
116026  * @param {Object} config A data object in the format:<pre><code>
116027 {
116028     name: [name],
116029     value: [value]
116030 }</code></pre>
116031  * The specified value's type
116032  * will be read automatically by the grid to determine the type of editor to use when displaying it.
116033  */
116034 Ext.define('Ext.grid.property.Property', {
116035     extend: 'Ext.data.Model',
116036
116037     alternateClassName: 'Ext.PropGridProperty',
116038
116039     fields: [{
116040         name: 'name',
116041         type: 'string'
116042     }, {
116043         name: 'value'
116044     }],
116045     idProperty: 'name'
116046 });
116047 /**
116048  * @class Ext.grid.property.Store
116049  * @extends Ext.data.Store
116050  * A custom {@link Ext.data.Store} for the {@link Ext.grid.property.Grid}. This class handles the mapping
116051  * between the custom data source objects supported by the grid and the {@link Ext.grid.property.Property} format
116052  * used by the {@link Ext.data.Store} base class.
116053  * @constructor
116054  * @param {Ext.grid.Grid} grid The grid this store will be bound to
116055  * @param {Object} source The source data config object
116056  */
116057 Ext.define('Ext.grid.property.Store', {
116058
116059     extend: 'Ext.data.Store',
116060
116061     alternateClassName: 'Ext.grid.PropertyStore',
116062
116063     uses: ['Ext.data.reader.Reader', 'Ext.data.proxy.Proxy', 'Ext.data.ResultSet', 'Ext.grid.property.Property'],
116064
116065     constructor : function(grid, source){
116066         var me = this;
116067         
116068         me.grid = grid;
116069         me.source = source;
116070         me.callParent([{
116071             data: source,
116072             model: Ext.grid.property.Property,
116073             proxy: me.getProxy()
116074         }]);
116075     },
116076
116077     // Return a singleton, customized Proxy object which configures itself with a custom Reader
116078     getProxy: function() {
116079         if (!this.proxy) {
116080             Ext.grid.property.Store.prototype.proxy = Ext.create('Ext.data.proxy.Memory', {
116081                 model: Ext.grid.property.Property,
116082                 reader: this.getReader()
116083             });
116084         }
116085         return this.proxy;
116086     },
116087
116088     // Return a singleton, customized Reader object which reads Ext.grid.property.Property records from an object.
116089     getReader: function() {
116090         if (!this.reader) {
116091             Ext.grid.property.Store.prototype.reader = Ext.create('Ext.data.reader.Reader', {
116092                 model: Ext.grid.property.Property,
116093
116094                 buildExtractors: Ext.emptyFn,
116095
116096                 read: function(dataObject) {
116097                     return this.readRecords(dataObject);
116098                 },
116099
116100                 readRecords: function(dataObject) {
116101                     var val,
116102                         propName,
116103                         result = {
116104                             records: [],
116105                             success: true
116106                         };
116107
116108                     for (propName in dataObject) {
116109                         if (dataObject.hasOwnProperty(propName)) {
116110                             val = dataObject[propName];
116111                             if (this.isEditableValue(val)) {
116112                                 result.records.push(new Ext.grid.property.Property({
116113                                     name: propName,
116114                                     value: val
116115                                 }, propName));
116116                             }
116117                         }
116118                     }
116119                     result.total = result.count = result.records.length;
116120                     return Ext.create('Ext.data.ResultSet', result);
116121                 },
116122
116123                 // private
116124                 isEditableValue: function(val){
116125                     return Ext.isPrimitive(val) || Ext.isDate(val);
116126                 }
116127             });
116128         }
116129         return this.reader;
116130     },
116131
116132     // protected - should only be called by the grid.  Use grid.setSource instead.
116133     setSource : function(dataObject) {
116134         var me = this;
116135
116136         me.source = dataObject;
116137         me.suspendEvents();
116138         me.removeAll();
116139         me.proxy.data = dataObject;
116140         me.load();
116141         me.resumeEvents();
116142         me.fireEvent('datachanged', me);
116143     },
116144
116145     // private
116146     getProperty : function(row) {
116147        return Ext.isNumber(row) ? this.getAt(row) : this.getById(row);
116148     },
116149
116150     // private
116151     setValue : function(prop, value, create){
116152         var me = this,
116153             rec = me.getRec(prop);
116154             
116155         if (rec) {
116156             rec.set('value', value);
116157             me.source[prop] = value;
116158         } else if (create) {
116159             // only create if specified.
116160             me.source[prop] = value;
116161             rec = new Ext.grid.property.Property({name: prop, value: value}, prop);
116162             me.store.add(rec);
116163         }
116164     },
116165
116166     // private
116167     remove : function(prop) {
116168         var rec = this.getRec(prop);
116169         if (rec) {
116170             store.remove(rec);
116171             delete this.source[prop];
116172         }
116173     },
116174
116175     // private
116176     getRec : function(prop) {
116177         return this.getById(prop);
116178     },
116179
116180     // protected - should only be called by the grid.  Use grid.getSource instead.
116181     getSource : function() {
116182         return this.source;
116183     }
116184 });
116185 /**
116186  * Component layout for components which maintain an inner body element which must be resized to synchronize with the
116187  * Component size.
116188  * @class Ext.layout.component.Body
116189  * @extends Ext.layout.component.Component
116190  * @private
116191  */
116192
116193 Ext.define('Ext.layout.component.Body', {
116194
116195     /* Begin Definitions */
116196
116197     alias: ['layout.body'],
116198
116199     extend: 'Ext.layout.component.Component',
116200
116201     uses: ['Ext.layout.container.Container'],
116202
116203     /* End Definitions */
116204
116205     type: 'body',
116206     
116207     onLayout: function(width, height) {
116208         var me = this,
116209             owner = me.owner;
116210
116211         // Size the Component's encapsulating element according to the dimensions
116212         me.setTargetSize(width, height);
116213
116214         // Size the Component's body element according to the content box of the encapsulating element
116215         me.setBodySize.apply(me, arguments);
116216
116217         // We need to bind to the owner whenever we do not have a user set height or width.
116218         if (owner && owner.layout && owner.layout.isLayout) {
116219             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
116220                 owner.layout.bindToOwnerCtComponent = true;
116221             }
116222             else {
116223                 owner.layout.bindToOwnerCtComponent = false;
116224             }
116225         }
116226         
116227         me.callParent(arguments);
116228     },
116229
116230     /**
116231      * @private
116232      * <p>Sizes the Component's body element to fit exactly within the content box of the Component's encapsulating element.<p>
116233      */
116234     setBodySize: function(width, height) {
116235         var me = this,
116236             owner = me.owner,
116237             frameSize = owner.frameSize,
116238             isNumber = Ext.isNumber;
116239
116240         if (isNumber(width)) {
116241             width -= owner.el.getFrameWidth('lr') - frameSize.left - frameSize.right;
116242         }
116243         if (isNumber(height)) {
116244             height -= owner.el.getFrameWidth('tb') - frameSize.top - frameSize.bottom;
116245         }
116246
116247         me.setElementSize(owner.body, width, height);
116248     }
116249 });
116250 /**
116251  * Component layout for Ext.form.FieldSet components
116252  * @class Ext.layout.component.FieldSet
116253  * @extends Ext.layout.component.Body
116254  * @private
116255  */
116256 Ext.define('Ext.layout.component.FieldSet', {
116257     extend: 'Ext.layout.component.Body',
116258     alias: ['layout.fieldset'],
116259
116260     type: 'fieldset',
116261
116262     doContainerLayout: function() {
116263         // Prevent layout/rendering of children if the fieldset is collapsed
116264         if (!this.owner.collapsed) {
116265             this.callParent();
116266         }
116267     }
116268 });
116269 /**
116270  * Component layout for tabs
116271  * @class Ext.layout.component.Tab
116272  * @extends Ext.layout.component.Button
116273  * @private
116274  */
116275 Ext.define('Ext.layout.component.Tab', {
116276
116277     alias: ['layout.tab'],
116278
116279     extend: 'Ext.layout.component.Button',
116280
116281     //type: 'button',
116282
116283     beforeLayout: function() {
116284         var me = this, dirty = me.lastClosable !== me.owner.closable;
116285
116286         if (dirty) {
116287             delete me.adjWidth;
116288         }
116289
116290         return this.callParent(arguments) || dirty;
116291     },
116292
116293     onLayout: function () {
116294         var me = this;
116295
116296         me.callParent(arguments);
116297
116298         me.lastClosable = me.owner.closable;
116299     }
116300 });
116301 /**
116302  * @private
116303  * @class Ext.layout.component.field.File
116304  * @extends Ext.layout.component.field.Field
116305  * Layout class for {@link Ext.form.field.File} fields. Adjusts the input field size to accommodate
116306  * the file picker trigger button.
116307  * @private
116308  */
116309
116310 Ext.define('Ext.layout.component.field.File', {
116311     alias: ['layout.filefield'],
116312     extend: 'Ext.layout.component.field.Field',
116313
116314     type: 'filefield',
116315
116316     sizeBodyContents: function(width, height) {
116317         var me = this,
116318             owner = me.owner;
116319
116320         if (!owner.buttonOnly) {
116321             // Decrease the field's width by the width of the button and the configured buttonMargin.
116322             // Both the text field and the button are floated left in CSS so they'll stack up side by side.
116323             me.setElementSize(owner.inputEl, Ext.isNumber(width) ? width - owner.button.getWidth() - owner.buttonMargin : width);
116324         }
116325     }
116326 });
116327 /**
116328  * @class Ext.layout.component.field.Slider
116329  * @extends Ext.layout.component.field.Field
116330  * @private
116331  */
116332
116333 Ext.define('Ext.layout.component.field.Slider', {
116334
116335     /* Begin Definitions */
116336
116337     alias: ['layout.sliderfield'],
116338
116339     extend: 'Ext.layout.component.field.Field',
116340
116341     /* End Definitions */
116342
116343     type: 'sliderfield',
116344
116345     sizeBodyContents: function(width, height) {
116346         var owner = this.owner,
116347             thumbs = owner.thumbs,
116348             length = thumbs.length,
116349             inputEl = owner.inputEl,
116350             innerEl = owner.innerEl,
116351             endEl = owner.endEl,
116352             i = 0;
116353
116354         /*
116355          * If we happen to be animating during a resize, the position of the thumb will likely be off
116356          * when the animation stops. As such, just stop any animations before syncing the thumbs.
116357          */
116358         for(; i < length; ++i) {
116359             thumbs[i].el.stopAnimation();
116360         }
116361         
116362         if (owner.vertical) {
116363             inputEl.setHeight(height);
116364             innerEl.setHeight(Ext.isNumber(height) ? height - inputEl.getPadding('t') - endEl.getPadding('b') : height);
116365         }
116366         else {
116367             inputEl.setWidth(width);
116368             innerEl.setWidth(Ext.isNumber(width) ? width - inputEl.getPadding('l') - endEl.getPadding('r') : width);
116369         }
116370         owner.syncThumbs();
116371     }
116372 });
116373
116374 /**
116375  * @class Ext.layout.container.Absolute
116376  * @extends Ext.layout.container.Anchor
116377  * <p>This is a layout that inherits the anchoring of <b>{@link Ext.layout.container.Anchor}</b> and adds the
116378  * ability for x/y positioning using the standard x and y component config options.</p>
116379  * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.container.Container#layout layout}</b></tt>
116380  * configuration property.  See <tt><b>{@link Ext.container.Container#layout}</b></tt> for additional details.</p>
116381  * {@img Ext.layout.container.Absolute/Ext.layout.container.Absolute.png Ext.layout.container.Absolute container layout}
116382  * <p>Example usage:</p>
116383  * <pre><code>
116384 Ext.create('Ext.form.Panel', {
116385     title: 'Absolute Layout',
116386     width: 300,
116387     height: 275,
116388     layout:'absolute',
116389     layoutConfig: {
116390         // layout-specific configs go here
116391         //itemCls: 'x-abs-layout-item',
116392     },
116393     url:'save-form.php',
116394     defaultType: 'textfield',
116395     items: [{
116396         x: 10,
116397         y: 10,
116398         xtype:'label',
116399         text: 'Send To:'
116400     },{
116401         x: 80,
116402         y: 10,
116403         name: 'to',
116404         anchor:'90%'  // anchor width by percentage
116405     },{
116406         x: 10,
116407         y: 40,
116408         xtype:'label',
116409         text: 'Subject:'
116410     },{
116411         x: 80,
116412         y: 40,
116413         name: 'subject',
116414         anchor: '90%'  // anchor width by percentage
116415     },{
116416         x:0,
116417         y: 80,
116418         xtype: 'textareafield',
116419         name: 'msg',
116420         anchor: '100% 100%'  // anchor width and height
116421     }],
116422     renderTo: Ext.getBody()
116423 });
116424 </code></pre>
116425  */
116426
116427 Ext.define('Ext.layout.container.Absolute', {
116428
116429     /* Begin Definitions */
116430
116431     alias: 'layout.absolute',
116432     extend: 'Ext.layout.container.Anchor',
116433     requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
116434     alternateClassName: 'Ext.layout.AbsoluteLayout',
116435
116436     /* End Definitions */
116437
116438     itemCls: Ext.baseCSSPrefix + 'abs-layout-item',
116439
116440     type: 'absolute',
116441
116442     onLayout: function() {
116443         var me = this,
116444             target = me.getTarget(),
116445             targetIsBody = target.dom === document.body;
116446
116447         // Do not set position: relative; when the absolute layout target is the body
116448         if (!targetIsBody) {
116449             target.position();
116450         }
116451         me.paddingLeft = target.getPadding('l');
116452         me.paddingTop = target.getPadding('t');
116453         me.callParent(arguments);
116454     },
116455
116456     // private
116457     adjustWidthAnchor: function(value, comp) {
116458         //return value ? value - comp.getPosition(true)[0] + this.paddingLeft: value;
116459         return value ? value - comp.getPosition(true)[0] : value;
116460     },
116461
116462     // private
116463     adjustHeightAnchor: function(value, comp) {
116464         //return value ? value - comp.getPosition(true)[1] + this.paddingTop: value;
116465         return value ? value - comp.getPosition(true)[1] : value;
116466     }
116467 });
116468 /**
116469  * @class Ext.layout.container.Accordion
116470  * @extends Ext.layout.container.VBox
116471  * <p>This is a layout that manages multiple Panels in an expandable accordion style such that only
116472  * <b>one Panel can be expanded at any given time</b>. Each Panel has built-in support for expanding and collapsing.</p>
116473  * <p>Note: Only Ext.Panels <b>and all subclasses of Ext.panel.Panel</b> may be used in an accordion layout Container.</p>
116474  * {@img Ext.layout.container.Accordion/Ext.layout.container.Accordion.png Ext.layout.container.Accordion container layout}
116475  * <p>Example usage:</p>
116476  * <pre><code>
116477 Ext.create('Ext.panel.Panel', {
116478     title: 'Accordion Layout',
116479     width: 300,
116480     height: 300,
116481     layout:'accordion',
116482     defaults: {
116483         // applied to each contained panel
116484         bodyStyle: 'padding:15px'
116485     },
116486     layoutConfig: {
116487         // layout-specific configs go here
116488         titleCollapse: false,
116489         animate: true,
116490         activeOnTop: true
116491     },
116492     items: [{
116493         title: 'Panel 1',
116494         html: 'Panel content!'
116495     },{
116496         title: 'Panel 2',
116497         html: 'Panel content!'
116498     },{
116499         title: 'Panel 3',
116500         html: 'Panel content!'
116501     }],
116502     renderTo: Ext.getBody()
116503 });
116504 </code></pre>
116505  */
116506 Ext.define('Ext.layout.container.Accordion', {
116507     extend: 'Ext.layout.container.VBox',
116508     alias: ['layout.accordion'],
116509     alternateClassName: 'Ext.layout.AccordionLayout',
116510     
116511     align: 'stretch',
116512
116513     /**
116514      * @cfg {Boolean} fill
116515      * True to adjust the active item's height to fill the available space in the container, false to use the
116516      * item's current height, or auto height if not explicitly set (defaults to true).
116517      */
116518     fill : true,
116519     /**
116520      * @cfg {Boolean} autoWidth
116521      * <p><b>This config is ignored in ExtJS 4.x.</b></p>
116522      * Child Panels have their width actively managed to fit within the accordion's width.
116523      */
116524     autoWidth : true,
116525     /**
116526      * @cfg {Boolean} titleCollapse
116527      * <p><b>Not implemented in PR2.</b></p>
116528      * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
116529      * expand/collapse only when the toggle tool button is clicked (defaults to true).  When set to false,
116530      * {@link #hideCollapseTool} should be false also.
116531      */
116532     titleCollapse : true,
116533     /**
116534      * @cfg {Boolean} hideCollapseTool
116535      * True to hide the contained Panels' collapse/expand toggle buttons, false to display them (defaults to false).
116536      * When set to true, {@link #titleCollapse} is automatically set to <code>true</code>.
116537      */
116538     hideCollapseTool : false,
116539     /**
116540      * @cfg {Boolean} collapseFirst
116541      * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
116542      * in the contained Panels' title bars, false to render it last (defaults to false).
116543      */
116544     collapseFirst : false,
116545     /**
116546      * @cfg {Boolean} animate
116547      * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
116548      * close directly with no animation (defaults to <code>true</code>). Note: The layout performs animated collapsing
116549      * and expanding, <i>not</i> the child Panels.
116550      */
116551     animate : true,
116552     /**
116553      * @cfg {Boolean} activeOnTop
116554      * <p><b>Not implemented in PR4.</b></p>
116555      * <p>Only valid when {@link #multi" is <code>false</code>.</p>
116556      * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
116557      * false to keep the panels in the rendered order. <b>This is NOT compatible with "animate:true"</b> (defaults to false).
116558      */
116559     activeOnTop : false,
116560     /**
116561      * @cfg {Boolean} multi
116562      * Defaults to <code>false</code>. Set to <code>true</code> to enable multiple accordion items to be open at once.
116563      */
116564     multi: false,
116565
116566     constructor: function() {
116567         var me = this;
116568
116569         me.callParent(arguments);
116570
116571         // animate flag must be false during initial render phase so we don't get animations.
116572         me.initialAnimate = me.animate;
116573         me.animate = false;
116574
116575         // Child Panels are not absolutely positioned if we are not filling, so use a different itemCls.
116576         if (me.fill === false) {
116577             me.itemCls = Ext.baseCSSPrefix + 'accordion-item';
116578         }
116579     },
116580
116581     // Cannot lay out a fitting accordion before we have been allocated a height.
116582     // So during render phase, layout will not be performed.
116583     beforeLayout: function() {
116584         var me = this;
116585
116586         me.callParent(arguments);
116587         if (me.fill) {
116588             if (!me.owner.el.dom.style.height) {
116589                 return false;
116590             }
116591         } else {
116592             me.owner.componentLayout.monitorChildren = false;
116593             me.autoSize = true;
116594             me.owner.setAutoScroll(true);
116595         }
116596     },
116597
116598     renderItems : function(items, target) {
116599         var me = this,
116600             ln = items.length,
116601             i = 0,
116602             comp,
116603             targetSize = me.getLayoutTargetSize(),
116604             renderedPanels = [],
116605             border;
116606
116607         for (; i < ln; i++) {
116608             comp = items[i];
116609             if (!comp.rendered) {
116610                 renderedPanels.push(comp);
116611
116612                 // Set up initial properties for Panels in an accordion.
116613                 if (me.collapseFirst) {
116614                     comp.collapseFirst = me.collapseFirst;
116615                 }
116616                 if (me.hideCollapseTool) {
116617                     comp.hideCollapseTool = me.hideCollapseTool;
116618                     comp.titleCollapse = true;
116619                 }
116620                 else if (me.titleCollapse) {
116621                     comp.titleCollapse = me.titleCollapse;
116622                 }
116623
116624                 delete comp.hideHeader;
116625                 comp.collapsible = true;
116626                 comp.title = comp.title || '&#160;';
116627                 comp.setBorder(false);
116628
116629                 // Set initial sizes
116630                 comp.width = targetSize.width;
116631                 if (me.fill) {
116632                     delete comp.height;
116633                     delete comp.flex;
116634
116635                     // If there is an expanded item, all others must be rendered collapsed.
116636                     if (me.expandedItem !== undefined) {
116637                         comp.collapsed = true;
116638                     }
116639                     // Otherwise expand the first item with collapsed explicitly configured as false
116640                     else if (comp.collapsed === false) {
116641                         comp.flex = 1;
116642                         me.expandedItem = i;
116643                     } else {
116644                         comp.collapsed = true;
116645                     }
116646                 } else {
116647                     delete comp.flex;
116648                     comp.animCollapse = me.initialAnimate;
116649                     comp.autoHeight = true;
116650                     comp.autoScroll = false;
116651                 }
116652             }
116653         }
116654
116655         // If no collapsed:false Panels found, make the first one expanded.
116656         if (ln && me.expandedItem === undefined) {
116657             me.expandedItem = 0;
116658             comp = items[0];
116659             comp.collapsed = false;
116660             if (me.fill) {
116661                 comp.flex = 1;
116662             }
116663         }
116664         
116665         // Render all Panels.
116666         me.callParent(arguments);
116667                 
116668         // Postprocess rendered Panels.
116669         ln = renderedPanels.length;
116670         for (i = 0; i < ln; i++) {
116671             comp = renderedPanels[i];
116672
116673             // Delete the dimension property so that our align: 'stretch' processing manages the width from here
116674             delete comp.width;
116675
116676             comp.header.addCls(Ext.baseCSSPrefix + 'accordion-hd');
116677             comp.body.addCls(Ext.baseCSSPrefix + 'accordion-body');
116678             
116679             // If we are fitting, then intercept expand/collapse requests. 
116680             if (me.fill) {
116681                 me.owner.mon(comp, {
116682                     show: me.onComponentShow,
116683                     beforeexpand: me.onComponentExpand,
116684                     beforecollapse: me.onComponentCollapse,
116685                     scope: me
116686                 });
116687             }
116688         }
116689     },
116690
116691     onLayout: function() {
116692         var me = this;
116693         
116694         me.updatePanelClasses();
116695                 
116696         if (me.fill) {
116697             me.callParent(arguments);
116698         } else {
116699             var targetSize = me.getLayoutTargetSize(),
116700                 items = me.getVisibleItems(),
116701                 len = items.length,
116702                 i = 0, comp;
116703
116704             for (; i < len; i++) {
116705                 comp = items[i];
116706                 if (comp.collapsed) {
116707                     items[i].setWidth(targetSize.width);
116708                 } else {
116709                     items[i].setSize(null, null);
116710                 }
116711             }
116712         }
116713         
116714         return me;
116715     },
116716     
116717     updatePanelClasses: function() {
116718         var children = this.getLayoutItems(),
116719             ln = children.length,
116720             siblingCollapsed = true,
116721             i, child;
116722             
116723         for (i = 0; i < ln; i++) {
116724             child = children[i];
116725             if (!siblingCollapsed) {
116726                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
116727             }
116728             else {
116729                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
116730             }
116731             if (i + 1 == ln && child.collapsed) {
116732                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
116733             }
116734             else {
116735                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
116736             }
116737             siblingCollapsed = child.collapsed;
116738         }
116739     },
116740
116741     // When a Component expands, adjust the heights of the other Components to be just enough to accommodate
116742     // their headers.
116743     // The expanded Component receives the only flex value, and so gets all remaining space.
116744     onComponentExpand: function(toExpand) {
116745         var me = this,
116746             it = me.owner.items.items,
116747             len = it.length,
116748             i = 0,
116749             comp;
116750
116751         for (; i < len; i++) {
116752             comp = it[i];
116753             if (comp === toExpand && comp.collapsed) {
116754                 me.setExpanded(comp);
116755             } else if (!me.multi && (comp.rendered && comp.header.rendered && comp !== toExpand && !comp.collapsed)) {
116756                 me.setCollapsed(comp);
116757             }
116758         }
116759         
116760         me.animate = me.initialAnimate;
116761         me.layout();
116762         me.animate = false;
116763         return false;
116764     },
116765
116766     onComponentCollapse: function(comp) {
116767         var me = this,
116768             toExpand = comp.next() || comp.prev(),
116769             expanded = me.multi ? me.owner.query('>panel:not([collapsed])') : [];
116770
116771         // If we are allowing multi, and the "toCollapse" component is NOT the only expanded Component,
116772         // then ask the box layout to collapse it to its header.
116773         if (me.multi) {
116774             me.setCollapsed(comp);
116775
116776             // If the collapsing Panel is the only expanded one, expand the following Component.
116777             // All this is handling fill: true, so there must be at least one expanded,
116778             if (expanded.length === 1 && expanded[0] === comp) {
116779                 me.setExpanded(toExpand);
116780             }
116781             
116782             me.animate = me.initialAnimate;
116783             me.layout();
116784             me.animate = false;
116785         }
116786         // Not allowing multi: expand the next sibling if possible, prev sibling if we collapsed the last
116787         else if (toExpand) {
116788             me.onComponentExpand(toExpand);
116789         }
116790         return false;
116791     },
116792
116793     onComponentShow: function(comp) {
116794         // Showing a Component means that you want to see it, so expand it.
116795         this.onComponentExpand(comp);
116796     },
116797
116798     setCollapsed: function(comp) {
116799         var otherDocks = comp.getDockedItems(),
116800             dockItem,
116801             len = otherDocks.length,
116802             i = 0;
116803
116804         // Hide all docked items except the header
116805         comp.hiddenDocked = [];
116806         for (; i < len; i++) {
116807             dockItem = otherDocks[i];
116808             if ((dockItem !== comp.header) && !dockItem.hidden) {
116809                 dockItem.hidden = true;
116810                 comp.hiddenDocked.push(dockItem);
116811             }
116812         }
116813         comp.addCls(comp.collapsedCls);
116814         comp.header.addCls(comp.collapsedHeaderCls);
116815         comp.height = comp.header.getHeight();
116816         comp.el.setHeight(comp.height);
116817         comp.collapsed = true;
116818         delete comp.flex;
116819         comp.fireEvent('collapse', comp);
116820         if (comp.collapseTool) {
116821             comp.collapseTool.setType('expand-' + comp.getOppositeDirection(comp.collapseDirection));
116822         }
116823     },
116824
116825     setExpanded: function(comp) {
116826         var otherDocks = comp.hiddenDocked,
116827             len = otherDocks ? otherDocks.length : 0,
116828             i = 0;
116829
116830         // Show temporarily hidden docked items
116831         for (; i < len; i++) {
116832             otherDocks[i].show();
116833         }
116834
116835         // If it was an initial native collapse which hides the body
116836         if (!comp.body.isVisible()) {
116837             comp.body.show();
116838         }
116839         delete comp.collapsed;
116840         delete comp.height;
116841         delete comp.componentLayout.lastComponentSize;
116842         comp.suspendLayout = false;
116843         comp.flex = 1;
116844         comp.removeCls(comp.collapsedCls);
116845         comp.header.removeCls(comp.collapsedHeaderCls);
116846         comp.fireEvent('expand', comp);
116847         if (comp.collapseTool) {
116848             comp.collapseTool.setType('collapse-' + comp.collapseDirection);
116849         }
116850         comp.setAutoScroll(comp.initialConfig.autoScroll);
116851     }
116852 });
116853 /**
116854  * @class Ext.resizer.Splitter
116855  * @extends Ext.Component
116856  * <p>This class functions <b>between siblings of a {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox}
116857  * layout</b> to resize both immediate siblings.</p>
116858  * <p>By default it will set the size of both siblings. <b>One</b> of the siblings may be configured with
116859  * <code>{@link Ext.Component#maintainFlex maintainFlex}: true</code> which will cause it not to receive a new size explicitly, but to be resized
116860  * by the layout.</p>
116861  * <p>A Splitter may be configured to show a centered mini-collapse tool orientated to collapse the {@link #collapseTarget}.
116862  * The Splitter will then call that sibling Panel's {@link Ext.panel.Panel#collapse collapse} or {@link Ext.panel.Panel#expand expand} method
116863  * to perform the appropriate operation (depending on the sibling collapse state). To create the mini-collapse tool but take care
116864  * of collapsing yourself, configure the splitter with <code>{@link #performCollapse} false</code>.</p>
116865  *
116866  * @xtype splitter
116867  */
116868 Ext.define('Ext.resizer.Splitter', {
116869     extend: 'Ext.Component',
116870     requires: ['Ext.XTemplate'],
116871     uses: ['Ext.resizer.SplitterTracker'],
116872     alias: 'widget.splitter',
116873
116874     renderTpl: [
116875         '<tpl if="collapsible===true"><div class="' + Ext.baseCSSPrefix + 'collapse-el ' + Ext.baseCSSPrefix + 'layout-split-{collapseDir}">&nbsp;</div></tpl>'
116876     ],
116877
116878     baseCls: Ext.baseCSSPrefix + 'splitter',
116879     collapsedCls: Ext.baseCSSPrefix + 'splitter-collapsed',
116880
116881     /**
116882      * @cfg {Boolean} collapsible
116883      * <code>true</code> to show a mini-collapse tool in the Splitter to toggle expand and collapse on the {@link #collapseTarget} Panel.
116884      * Defaults to the {@link Ext.panel.Panel#collapsible collapsible} setting of the Panel.
116885      */
116886     collapsible: false,
116887
116888     /**
116889      * @cfg {Boolean} performCollapse
116890      * <p>Set to <code>false</code> to prevent this Splitter's mini-collapse tool from managing the collapse
116891      * state of the {@link #collapseTarget}.</p>
116892      */
116893
116894     /**
116895      * @cfg {Boolean} collapseOnDblClick
116896      * <code>true</code> to enable dblclick to toggle expand and collapse on the {@link #collapseTarget} Panel.
116897      */
116898     collapseOnDblClick: true,
116899
116900     /**
116901      * @cfg {Number} defaultSplitMin
116902      * Provides a default minimum width or height for the two components
116903      * that the splitter is between.
116904      */
116905     defaultSplitMin: 40,
116906
116907     /**
116908      * @cfg {Number} defaultSplitMax
116909      * Provides a default maximum width or height for the two components
116910      * that the splitter is between.
116911      */
116912     defaultSplitMax: 1000,
116913
116914     width: 5,
116915     height: 5,
116916
116917     /**
116918      * @cfg {Mixed} collapseTarget
116919      * <p>A string describing the relative position of the immediate sibling Panel to collapse. May be 'prev' or 'next' (Defaults to 'next')</p>
116920      * <p>Or the immediate sibling Panel to collapse.</p>
116921      * <p>The orientation of the mini-collapse tool will be inferred from this setting.</p>
116922      * <p><b>Note that only Panels may be collapsed.</b></p>
116923      */
116924     collapseTarget: 'next',
116925
116926     /**
116927      * @property orientation
116928      * @type String
116929      * Orientation of this Splitter. <code>'vertical'</code> when used in an hbox layout, <code>'horizontal'</code>
116930      * when used in a vbox layout.
116931      */
116932
116933     onRender: function() {
116934         var me = this,
116935             target = me.getCollapseTarget(),
116936             collapseDir = me.getCollapseDirection();
116937
116938         Ext.applyIf(me.renderData, {
116939             collapseDir: collapseDir,
116940             collapsible: me.collapsible || target.collapsible
116941         });
116942         Ext.applyIf(me.renderSelectors, {
116943             collapseEl: '.' + Ext.baseCSSPrefix + 'collapse-el'
116944         });
116945
116946         this.callParent(arguments);
116947
116948         // Add listeners on the mini-collapse tool unless performCollapse is set to false
116949         if (me.performCollapse !== false) {
116950             if (me.renderData.collapsible) {
116951                 me.mon(me.collapseEl, 'click', me.toggleTargetCmp, me);
116952             }
116953             if (me.collapseOnDblClick) {
116954                 me.mon(me.el, 'dblclick', me.toggleTargetCmp, me);
116955             }
116956         }
116957
116958         // Ensure the mini collapse icon is set to the correct direction when the target is collapsed/expanded by any means
116959         me.mon(target, 'collapse', me.onTargetCollapse, me);
116960         me.mon(target, 'expand', me.onTargetExpand, me);
116961
116962         me.el.addCls(me.baseCls + '-' + me.orientation);
116963         me.el.unselectable();
116964
116965         me.tracker = Ext.create('Ext.resizer.SplitterTracker', {
116966             el: me.el
116967         });
116968
116969         // Relay the most important events to our owner (could open wider later):
116970         me.relayEvents(me.tracker, [ 'beforedragstart', 'dragstart', 'dragend' ]);
116971     },
116972
116973     getCollapseDirection: function() {
116974         var me = this,
116975             idx,
116976             type = me.ownerCt.layout.type;
116977
116978         // Avoid duplication of string tests.
116979         // Create a two bit truth table of the configuration of the Splitter:
116980         // Collapse Target | orientation
116981         //        0              0             = next, horizontal
116982         //        0              1             = next, vertical
116983         //        1              0             = prev, horizontal
116984         //        1              1             = prev, vertical
116985         if (me.collapseTarget.isComponent) {
116986             idx = Number(me.ownerCt.items.indexOf(me.collapseTarget) == me.ownerCt.items.indexOf(me) - 1) << 1 | Number(type == 'hbox');
116987         } else {
116988             idx = Number(me.collapseTarget == 'prev') << 1 | Number(type == 'hbox');
116989         }
116990
116991         // Read the data out the truth table
116992         me.orientation = ['horizontal', 'vertical'][idx & 1];
116993         return ['bottom', 'right', 'top', 'left'][idx];
116994     },
116995
116996     getCollapseTarget: function() {
116997         return this.collapseTarget.isComponent ? this.collapseTarget : this.collapseTarget == 'prev' ? this.previousSibling() : this.nextSibling();
116998     },
116999
117000     onTargetCollapse: function(target) {
117001         this.el.addCls(this.collapsedCls);
117002     },
117003
117004     onTargetExpand: function(target) {
117005         this.el.removeCls(this.collapsedCls);
117006     },
117007
117008     toggleTargetCmp: function(e, t) {
117009         var cmp = this.getCollapseTarget();
117010
117011         if (cmp.isVisible()) {
117012             // restore
117013             if (cmp.collapsed) {
117014                 cmp.expand(cmp.animCollapse);
117015             // collapse
117016             } else {
117017                 cmp.collapse(this.renderData.collapseDir, cmp.animCollapse);
117018             }
117019         }
117020     },
117021
117022     /*
117023      * Work around IE bug. %age margins do not get recalculated on element resize unless repaint called.
117024      */
117025     setSize: function() {
117026         var me = this;
117027         me.callParent(arguments);
117028         if (Ext.isIE) {
117029             me.el.repaint();
117030         }
117031     }
117032 });
117033
117034 /**
117035  * @class Ext.layout.container.Border
117036  * @extends Ext.layout.container.Container
117037  * <p>This is a multi-pane, application-oriented UI layout style that supports multiple
117038  * nested panels, automatic bars between regions and built-in
117039  * {@link Ext.panel.Panel#collapsible expanding and collapsing} of regions.</p>
117040  * <p>This class is intended to be extended or created via the <code>layout:'border'</code>
117041  * {@link Ext.container.Container#layout} config, and should generally not need to be created directly
117042  * via the new keyword.</p>
117043  * {@img Ext.layout.container.Border/Ext.layout.container.Border.png Ext.layout.container.Border container layout}
117044  * <p>Example usage:</p>
117045  * <pre><code>
117046      Ext.create('Ext.panel.Panel', {
117047         width: 500,
117048         height: 400,
117049         title: 'Border Layout',
117050         layout: 'border',
117051         items: [{
117052             title: 'South Region is resizable',
117053             region: 'south',     // position for region
117054             xtype: 'panel',
117055             height: 100,
117056             split: true,         // enable resizing
117057             margins: '0 5 5 5'
117058         },{
117059             // xtype: 'panel' implied by default
117060             title: 'West Region is collapsible',
117061             region:'west',
117062             xtype: 'panel',
117063             margins: '5 0 0 5',
117064             width: 200,
117065             collapsible: true,   // make collapsible
117066             id: 'west-region-container',
117067             layout: 'fit'
117068         },{
117069             title: 'Center Region',
117070             region: 'center',     // center region is required, no width/height specified
117071             xtype: 'panel',
117072             layout: 'fit',
117073             margins: '5 5 0 0'
117074         }],
117075         renderTo: Ext.getBody()
117076     });
117077 </code></pre>
117078  * <p><b><u>Notes</u></b>:</p><div class="mdetail-params"><ul>
117079  * <li>Any Container using the Border layout <b>must</b> have a child item with <code>region:'center'</code>.
117080  * The child item in the center region will always be resized to fill the remaining space not used by
117081  * the other regions in the layout.</li>
117082  * <li>Any child items with a region of <code>west</code> or <code>east</code> may be configured with either
117083  * an initial <code>width</code>, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage width <b>string</b> (Which is simply divided by 100 and used as a flex value). The 'center' region has a flex value of <code>1</code>.</li>
117084  * <li>Any child items with a region of <code>north</code> or <code>south</code> may be configured with either
117085  * an initial <code>height</code>, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage height <b>string</b> (Which is simply divided by 100 and used as a flex value). The 'center' region has a flex value of <code>1</code>.</li>
117086  * <li>The regions of a BorderLayout are <b>fixed at render time</b> and thereafter, its child Components may not be removed or added</b>.To add/remove
117087  * Components within a BorderLayout, have them wrapped by an additional Container which is directly
117088  * managed by the BorderLayout.  If the region is to be collapsible, the Container used directly
117089  * by the BorderLayout manager should be a Panel.  In the following example a Container (an Ext.panel.Panel)
117090  * is added to the west region:<pre><code>
117091 wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
117092 wrc.{@link Ext.container.Container#removeAll removeAll}();
117093 wrc.{@link Ext.container.Container#add add}({
117094     title: 'Added Panel',
117095     html: 'Some content'
117096 });
117097  * </code></pre>
117098  * </li>
117099  * <li><b>There is no BorderLayout.Region class in ExtJS 4.0+</b></li>
117100  * </ul></div>
117101  */
117102 Ext.define('Ext.layout.container.Border', {
117103
117104     alias: ['layout.border'],
117105     extend: 'Ext.layout.container.Container',
117106     requires: ['Ext.resizer.Splitter', 'Ext.container.Container', 'Ext.fx.Anim'],
117107     alternateClassName: 'Ext.layout.BorderLayout',
117108
117109     targetCls: Ext.baseCSSPrefix + 'border-layout-ct',
117110
117111     itemCls: Ext.baseCSSPrefix + 'border-item',
117112
117113     bindToOwnerCtContainer: true,
117114
117115     fixedLayout: false,
117116
117117     percentageRe: /(\d+)%/,
117118
117119     slideDirection: {
117120         north: 't',
117121         south: 'b',
117122         west: 'l',
117123         east: 'r'
117124     },
117125
117126     constructor: function(config) {
117127         this.initialConfig = config;
117128         this.callParent(arguments);
117129     },
117130
117131     onLayout: function() {
117132         var me = this;
117133         if (!me.borderLayoutInitialized) {
117134             me.initializeBorderLayout();
117135         }
117136
117137         // Delegate this operation to the shadow "V" or "H" box layout, and then down to any embedded layout.
117138         me.fixHeightConstraints();
117139         me.shadowLayout.onLayout();
117140         if (me.embeddedContainer) {
117141             me.embeddedContainer.layout.onLayout();
117142         }
117143
117144         // If the panel was originally configured with collapsed: true, it will have
117145         // been initialized with a "borderCollapse" flag: Collapse it now before the first layout.
117146         if (!me.initialCollapsedComplete) {
117147             Ext.iterate(me.regions, function(name, region){
117148                 if (region.borderCollapse) {
117149                     me.onBeforeRegionCollapse(region, region.collapseDirection, false, 0);
117150                 }
117151             });
117152             me.initialCollapsedComplete = true;
117153         }
117154     },
117155
117156     isValidParent : function(item, target, position) {
117157         if (!this.borderLayoutInitialized) {
117158             this.initializeBorderLayout();
117159         }
117160
117161         // Delegate this operation to the shadow "V" or "H" box layout.
117162         return this.shadowLayout.isValidParent(item, target, position);
117163     },
117164
117165     beforeLayout: function() {
117166         if (!this.borderLayoutInitialized) {
117167             this.initializeBorderLayout();
117168         }
117169
117170         // Delegate this operation to the shadow "V" or "H" box layout.
117171         this.shadowLayout.beforeLayout();
117172     },
117173
117174     renderItems: function(items, target) {
117175         Ext.Error.raise('This should not be called');
117176     },
117177
117178     renderItem: function(item) {
117179         Ext.Error.raise('This should not be called');
117180     },
117181
117182     initializeBorderLayout: function() {
117183         var me = this,
117184             i = 0,
117185             items = me.getLayoutItems(),
117186             ln = items.length,
117187             regions = (me.regions = {}),
117188             vBoxItems = [],
117189             hBoxItems = [],
117190             horizontalFlex = 0,
117191             verticalFlex = 0,
117192             comp, percentage;
117193
117194         // Map of Splitters for each region
117195         me.splitters = {};
117196
117197         // Map of regions
117198         for (; i < ln; i++) {
117199             comp = items[i];
117200             regions[comp.region] = comp;
117201
117202             // Intercept collapsing to implement showing an alternate Component as a collapsed placeholder
117203             if (comp.region != 'center' && comp.collapsible && comp.collapseMode != 'header') {
117204
117205                 // This layout intercepts any initial collapsed state. Panel must not do this itself.
117206                 comp.borderCollapse = comp.collapsed;
117207                 delete comp.collapsed;
117208
117209                 comp.on({
117210                     beforecollapse: me.onBeforeRegionCollapse,
117211                     beforeexpand: me.onBeforeRegionExpand,
117212                     destroy: me.onRegionDestroy,
117213                     scope: me
117214                 });
117215                 me.setupState(comp);
117216             }
117217         }
117218         if (!regions.center) {
117219             Ext.Error.raise("You must specify a center region when defining a BorderLayout.");
117220         }
117221         comp = regions.center;
117222         if (!comp.flex) {
117223             comp.flex = 1;
117224         }
117225         delete comp.width;
117226         comp.maintainFlex = true;
117227
117228         // Begin the VBox and HBox item list.
117229         comp = regions.west;
117230         if (comp) {
117231             comp.collapseDirection = Ext.Component.DIRECTION_LEFT;
117232             hBoxItems.push(comp);
117233             if (comp.split) {
117234                 hBoxItems.push(me.splitters.west = me.createSplitter(comp));
117235             }
117236             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
117237             if (percentage) {
117238                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
117239                 delete comp.width;
117240             }
117241         }
117242         comp = regions.north;
117243         if (comp) {
117244             comp.collapseDirection = Ext.Component.DIRECTION_TOP;
117245             vBoxItems.push(comp);
117246             if (comp.split) {
117247                 vBoxItems.push(me.splitters.north = me.createSplitter(comp));
117248             }
117249             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
117250             if (percentage) {
117251                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
117252                 delete comp.height;
117253             }
117254         }
117255
117256         // Decide into which Collection the center region goes.
117257         if (regions.north || regions.south) {
117258             if (regions.east || regions.west) {
117259
117260                 // Create the embedded center. Mark it with the region: 'center' property so that it can be identified as the center.
117261                 vBoxItems.push(me.embeddedContainer = Ext.create('Ext.container.Container', {
117262                     xtype: 'container',
117263                     region: 'center',
117264                     id: me.owner.id + '-embedded-center',
117265                     cls: Ext.baseCSSPrefix + 'border-item',
117266                     flex: regions.center.flex,
117267                     maintainFlex: true,
117268                     layout: {
117269                         type: 'hbox',
117270                         align: 'stretch'
117271                     }
117272                 }));
117273                 hBoxItems.push(regions.center);
117274             }
117275             // No east or west: the original center goes straight into the vbox
117276             else {
117277                 vBoxItems.push(regions.center);
117278             }
117279         }
117280         // If we have no north or south, then the center is part of the HBox items
117281         else {
117282             hBoxItems.push(regions.center);
117283         }
117284
117285         // Finish off the VBox and HBox item list.
117286         comp = regions.south;
117287         if (comp) {
117288             comp.collapseDirection = Ext.Component.DIRECTION_BOTTOM;
117289             if (comp.split) {
117290                 vBoxItems.push(me.splitters.south = me.createSplitter(comp));
117291             }
117292             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
117293             if (percentage) {
117294                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
117295                 delete comp.height;
117296             }
117297             vBoxItems.push(comp);
117298         }
117299         comp = regions.east;
117300         if (comp) {
117301             comp.collapseDirection = Ext.Component.DIRECTION_RIGHT;
117302             if (comp.split) {
117303                 hBoxItems.push(me.splitters.east = me.createSplitter(comp));
117304             }
117305             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
117306             if (percentage) {
117307                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
117308                 delete comp.width;
117309             }
117310             hBoxItems.push(comp);
117311         }
117312
117313         // Create the injected "items" collections for the Containers.
117314         // If we have north or south, then the shadow Container will be a VBox.
117315         // If there are also east or west regions, its center will be a shadow HBox.
117316         // If there are *only* east or west regions, then the shadow layout will be an HBox (or Fit).
117317         if (regions.north || regions.south) {
117318
117319             me.shadowContainer = Ext.create('Ext.container.Container', {
117320                 ownerCt: me.owner,
117321                 el: me.getTarget(),
117322                 layout: Ext.applyIf({
117323                     type: 'vbox',
117324                     align: 'stretch'
117325                 }, me.initialConfig)
117326             });
117327             me.createItems(me.shadowContainer, vBoxItems);
117328
117329             // Allow the Splitters to orientate themselves
117330             if (me.splitters.north) {
117331                 me.splitters.north.ownerCt = me.shadowContainer;
117332             }
117333             if (me.splitters.south) {
117334                 me.splitters.south.ownerCt = me.shadowContainer;
117335             }
117336
117337             // Inject items into the HBox Container if there is one - if there was an east or west.
117338             if (me.embeddedContainer) {
117339                 me.embeddedContainer.ownerCt = me.shadowContainer;
117340                 me.createItems(me.embeddedContainer, hBoxItems);
117341
117342                 // Allow the Splitters to orientate themselves
117343                 if (me.splitters.east) {
117344                     me.splitters.east.ownerCt = me.embeddedContainer;
117345                 }
117346                 if (me.splitters.west) {
117347                     me.splitters.west.ownerCt = me.embeddedContainer;
117348                 }
117349
117350                 // These spliiters need to be constrained by components one-level below
117351                 // the component in their vobx. We update the min/maxHeight on the helper
117352                 // (embeddedContainer) prior to starting the split/drag. This has to be
117353                 // done on-the-fly to allow min/maxHeight of the E/C/W regions to be set
117354                 // dynamically.
117355                 Ext.each([me.splitters.north, me.splitters.south], function (splitter) {
117356                     if (splitter) {
117357                         splitter.on('beforedragstart', me.fixHeightConstraints, me);
117358                     }
117359                 });
117360
117361                 // The east or west region wanted a percentage
117362                 if (horizontalFlex) {
117363                     regions.center.flex -= horizontalFlex;
117364                 }
117365                 // The north or south region wanted a percentage
117366                 if (verticalFlex) {
117367                     me.embeddedContainer.flex -= verticalFlex;
117368                 }
117369             } else {
117370                 // The north or south region wanted a percentage
117371                 if (verticalFlex) {
117372                     regions.center.flex -= verticalFlex;
117373                 }
117374             }
117375         }
117376         // If we have no north or south, then there's only one Container, and it's
117377         // an HBox, or, if only a center region was specified, a Fit.
117378         else {
117379             me.shadowContainer = Ext.create('Ext.container.Container', {
117380                 ownerCt: me.owner,
117381                 el: me.getTarget(),
117382                 layout: Ext.applyIf({
117383                     type: (hBoxItems.length == 1) ? 'fit' : 'hbox',
117384                     align: 'stretch'
117385                 }, me.initialConfig)
117386             });
117387             me.createItems(me.shadowContainer, hBoxItems);
117388
117389             // Allow the Splitters to orientate themselves
117390             if (me.splitters.east) {
117391                 me.splitters.east.ownerCt = me.shadowContainer;
117392             }
117393             if (me.splitters.west) {
117394                 me.splitters.west.ownerCt = me.shadowContainer;
117395             }
117396
117397             // The east or west region wanted a percentage
117398             if (horizontalFlex) {
117399                 regions.center.flex -= verticalFlex;
117400             }
117401         }
117402
117403         // Create upward links from the region Components to their shadow ownerCts
117404         for (i = 0, items = me.shadowContainer.items.items, ln = items.length; i < ln; i++) {
117405             items[i].shadowOwnerCt = me.shadowContainer;
117406         }
117407         if (me.embeddedContainer) {
117408             for (i = 0, items = me.embeddedContainer.items.items, ln = items.length; i < ln; i++) {
117409                 items[i].shadowOwnerCt = me.embeddedContainer;
117410             }
117411         }
117412
117413         // This is the layout that we delegate all operations to
117414         me.shadowLayout = me.shadowContainer.getLayout();
117415
117416         me.borderLayoutInitialized = true;
117417     },
117418
117419     setupState: function(comp){
117420         var getState = comp.getState;
117421         comp.getState = function(){
117422             // call the original getState
117423             var state = getState.call(comp) || {},
117424                 region = comp.region;
117425
117426             state.collapsed = !!comp.collapsed;
117427             if (region == 'west' || region == 'east') {
117428                 state.width = comp.getWidth();
117429             } else {
117430                 state.height = comp.getHeight();
117431             }
117432             return state;
117433         };
117434         comp.addStateEvents(['collapse', 'expand', 'resize']);
117435     },
117436
117437     /**
117438      * Create the items collection for our shadow/embedded containers
117439      * @private
117440      */
117441     createItems: function(container, items){
117442         // Have to inject an items Collection *after* construction.
117443         // The child items of the shadow layout must retain their original, user-defined ownerCt
117444         delete container.items;
117445         container.initItems();
117446         container.items.addAll(items);
117447     },
117448
117449     // Private
117450     // Create a splitter for a child of the layout.
117451     createSplitter: function(comp) {
117452         var me = this,
117453             interceptCollapse = (comp.collapseMode != 'header'),
117454             resizer;
117455
117456         resizer = Ext.create('Ext.resizer.Splitter', {
117457             hidden: !!comp.hidden,
117458             collapseTarget: comp,
117459             performCollapse: !interceptCollapse,
117460             listeners: interceptCollapse ? {
117461                 click: {
117462                     fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
117463                     element: 'collapseEl'
117464                 }
117465             } : null
117466         });
117467
117468         // Mini collapse means that the splitter is the placeholder Component
117469         if (comp.collapseMode == 'mini') {
117470             comp.placeholder = resizer;
117471         }
117472
117473         // Arrange to hide/show a region's associated splitter when the region is hidden/shown
117474         comp.on({
117475             hide: me.onRegionVisibilityChange,
117476             show: me.onRegionVisibilityChange,
117477             scope: me
117478         });
117479         return resizer;
117480     },
117481
117482     // Private
117483     // Propogates the min/maxHeight values from the inner hbox items to its container.
117484     fixHeightConstraints: function () {
117485         var me = this,
117486             ct = me.embeddedContainer,
117487             maxHeight = 1e99, minHeight = -1;
117488
117489         if (!ct) {
117490             return;
117491         }
117492
117493         ct.items.each(function (item) {
117494             if (Ext.isNumber(item.maxHeight)) {
117495                 maxHeight = Math.max(maxHeight, item.maxHeight);
117496             }
117497             if (Ext.isNumber(item.minHeight)) {
117498                 minHeight = Math.max(minHeight, item.minHeight);
117499             }
117500         });
117501
117502         ct.maxHeight = maxHeight;
117503         ct.minHeight = minHeight;
117504     },
117505
117506     // Hide/show a region's associated splitter when the region is hidden/shown
117507     onRegionVisibilityChange: function(comp){
117508         this.splitters[comp.region][comp.hidden ? 'hide' : 'show']();
117509         this.layout();
117510     },
117511
117512     // Called when a splitter mini-collapse tool is clicked on.
117513     // The listener is only added if this layout is controlling collapsing,
117514     // not if the component's collapseMode is 'mini' or 'header'.
117515     onSplitterCollapseClick: function(comp) {
117516         if (comp.collapsed) {
117517             this.onPlaceHolderToolClick(null, null, null, {client: comp});
117518         } else {
117519             comp.collapse();
117520         }
117521     },
117522
117523     /**
117524      * <p>Return the {@link Ext.panel.Panel#placeholder placeholder} Component to which the passed child Panel of the layout will collapse.
117525      * By default, this will be a {@link Ext.panel.Header Header} component (Docked to the appropriate border). See {@link Ext.panel.Panel#placeholder placeholder}.
117526      * config to customize this.</p>
117527      * <p><b>Note that this will be a fully instantiated Component, but will only be <i>rendered</i> when the Panel is first collapsed.</b></p>
117528      * @param {Panel} panel The child Panel of the layout for which to return the {@link Ext.panel.Panel#placeholder placeholder}.
117529      * @returns {Component} The Panel's {@link Ext.panel.Panel#placeholder placeholder} unless the {@link Ext.panel.Panel#collapseMode collapseMode} is
117530      * <code>'header'</code>, in which case <i>undefined</i> is returned.
117531      */
117532     getPlaceholder: function(comp) {
117533         var me = this,
117534             placeholder = comp.placeholder,
117535             shadowContainer = comp.shadowOwnerCt,
117536             shadowLayout = shadowContainer.layout,
117537             oppositeDirection = Ext.panel.Panel.prototype.getOppositeDirection(comp.collapseDirection),
117538             horiz = (comp.region == 'north' || comp.region == 'south');
117539
117540         // No placeholder if the collapse mode is not the Border layout default
117541         if (comp.collapseMode == 'header') {
117542             return;
117543         }
117544
117545         // Provide a replacement Container with an expand tool
117546         if (!placeholder) {
117547             if (comp.collapseMode == 'mini') {
117548                 placeholder = Ext.create('Ext.resizer.Splitter', {
117549                     id: 'collapse-placeholder-' + comp.id,
117550                     collapseTarget: comp,
117551                     performCollapse: false,
117552                     listeners: {
117553                         click: {
117554                             fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
117555                             element: 'collapseEl'
117556                         }
117557                     }
117558                 });
117559                 placeholder.addCls(placeholder.collapsedCls);
117560             } else {
117561                 placeholder = {
117562                     id: 'collapse-placeholder-' + comp.id,
117563                     margins: comp.initialConfig.margins || Ext.getClass(comp).prototype.margins,
117564                     xtype: 'header',
117565                     orientation: horiz ? 'horizontal' : 'vertical',
117566                     title: comp.title,
117567                     textCls: comp.headerTextCls,
117568                     iconCls: comp.iconCls,
117569                     baseCls: comp.baseCls + '-header',
117570                     ui: comp.ui,
117571                     indicateDrag: comp.draggable,
117572                     cls: Ext.baseCSSPrefix + 'region-collapsed-placeholder ' + Ext.baseCSSPrefix + 'region-collapsed-' + comp.collapseDirection + '-placeholder',
117573                     listeners: comp.floatable ? {
117574                         click: {
117575                             fn: function(e) {
117576                                 me.floatCollapsedPanel(e, comp);
117577                             },
117578                             element: 'el'
117579                         }
117580                     } : null
117581                 };
117582                 // Hack for IE6/7/IEQuirks's inability to display an inline-block
117583                 if ((Ext.isIE6 || Ext.isIE7 || (Ext.isIEQuirks)) && !horiz) {
117584                     placeholder.width = 25;
117585                 }
117586                 if (!comp.hideCollapseTool) {
117587                     placeholder[horiz ? 'tools' : 'items'] = [{
117588                         xtype: 'tool',
117589                         client: comp,
117590                         type: 'expand-' + oppositeDirection,
117591                         handler: me.onPlaceHolderToolClick,
117592                         scope: me
117593                     }];
117594                 }
117595             }
117596             placeholder = me.owner.createComponent(placeholder);
117597             if (comp.isXType('panel')) {
117598                 comp.on({
117599                     titlechange: me.onRegionTitleChange,
117600                     iconchange: me.onRegionIconChange,
117601                     scope: me
117602                 });
117603             }
117604         }
117605
117606         // The collapsed Component holds a reference to its placeholder and vice versa
117607         comp.placeholder = placeholder;
117608         placeholder.comp = comp;
117609
117610         return placeholder;
117611     },
117612
117613     /**
117614      * @private
117615      * Update the placeholder title when panel title has been set or changed.
117616      */
117617     onRegionTitleChange: function(comp, newTitle) {
117618         comp.placeholder.setTitle(newTitle);
117619     },
117620
117621     /**
117622      * @private
117623      * Update the placeholder iconCls when panel iconCls has been set or changed.
117624      */
117625     onRegionIconChange: function(comp, newIconCls) {
117626         comp.placeholder.setIconCls(newIconCls);
117627     },
117628
117629     /**
117630      * @private
117631      * Calculates the size and positioning of the passed child item. Must be present because Panel's expand,
117632      * when configured with a flex, calls this method on its ownerCt's layout.
117633      * @param {Component} child The child Component to calculate the box for
117634      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
117635      */
117636     calculateChildBox: function(comp) {
117637         var me = this;
117638         if (me.shadowContainer.items.contains(comp)) {
117639             return me.shadowContainer.layout.calculateChildBox(comp);
117640         }
117641         else if (me.embeddedContainer && me.embeddedContainer.items.contains(comp)) {
117642             return me.embeddedContainer.layout.calculateChildBox(comp);
117643         }
117644     },
117645
117646     /**
117647      * @private
117648      * Intercepts the Panel's own collapse event and perform's substitution of the Panel
117649      * with a placeholder Header orientated in the appropriate dimension.
117650      * @param comp The Panel being collapsed.
117651      * @param direction
117652      * @param animate
117653      * @returns {Boolean} false to inhibit the Panel from performing its own collapse.
117654      */
117655     onBeforeRegionCollapse: function(comp, direction, animate) {
117656         var me = this,
117657             compEl = comp.el,
117658             width,
117659             miniCollapse = comp.collapseMode == 'mini',
117660             shadowContainer = comp.shadowOwnerCt,
117661             shadowLayout = shadowContainer.layout,
117662             placeholder = comp.placeholder,
117663             sl = me.owner.suspendLayout,
117664             scsl = shadowContainer.suspendLayout,
117665             isNorthOrWest = (comp.region == 'north' || comp.region == 'west'); // Flag to keep the placeholder non-adjacent to any Splitter
117666
117667         // Do not trigger a layout during transition to collapsed Component
117668         me.owner.suspendLayout = true;
117669         shadowContainer.suspendLayout = true;
117670
117671         // Prevent upward notifications from downstream layouts
117672         shadowLayout.layoutBusy = true;
117673         if (shadowContainer.componentLayout) {
117674             shadowContainer.componentLayout.layoutBusy = true;
117675         }
117676         me.shadowContainer.layout.layoutBusy = true;
117677         me.layoutBusy = true;
117678         me.owner.componentLayout.layoutBusy = true;
117679
117680         // Provide a replacement Container with an expand tool
117681         if (!placeholder) {
117682             placeholder = me.getPlaceholder(comp);
117683         }
117684
117685         // placeholder already in place; show it.
117686         if (placeholder.shadowOwnerCt === shadowContainer) {
117687             placeholder.show();
117688         }
117689         // Insert the collapsed placeholder Component into the appropriate Box layout shadow Container
117690         // It must go next to its client Component, but non-adjacent to the splitter so splitter can find its collapse client.
117691         // Inject an ownerCt value pointing to the owner, border layout Container as the user will expect.
117692         else {
117693             shadowContainer.insert(shadowContainer.items.indexOf(comp) + (isNorthOrWest ? 0 : 1), placeholder);
117694             placeholder.shadowOwnerCt = shadowContainer;
117695             placeholder.ownerCt = me.owner;
117696         }
117697
117698         // Flag the collapsing Component as hidden and show the placeholder.
117699         // This causes the shadow Box layout's calculateChildBoxes to calculate the correct new arrangement.
117700         // We hide or slideOut the Component's element
117701         comp.hidden = true;
117702
117703         if (!placeholder.rendered) {
117704             shadowLayout.renderItem(placeholder, shadowLayout.innerCt);
117705
117706             // The inserted placeholder does not have the proper size, so copy the width
117707             // for N/S or the height for E/W from the component. This fixes EXTJSIV-1562
117708             // without recursive layouts. This is only an issue initially. After this time,
117709             // placeholder will have the correct width/height set by the layout (which has
117710             // already happened when we get here initially).
117711             if (comp.region == 'north' || comp.region == 'south') {
117712                 placeholder.setCalculatedSize(comp.getWidth());
117713             } else {
117714                 placeholder.setCalculatedSize(undefined, comp.getHeight());
117715             }
117716         }
117717
117718         // Jobs to be done after the collapse has been done
117719         function afterCollapse() {
117720             // Reinstate automatic laying out.
117721             me.owner.suspendLayout = sl;
117722             shadowContainer.suspendLayout = scsl;
117723             delete shadowLayout.layoutBusy;
117724             if (shadowContainer.componentLayout) {
117725                 delete shadowContainer.componentLayout.layoutBusy;
117726             }
117727             delete me.shadowContainer.layout.layoutBusy;
117728             delete me.layoutBusy;
117729             delete me.owner.componentLayout.layoutBusy;
117730
117731             // Fire the collapse event: The Panel has in fact been collapsed, but by substitution of an alternative Component
117732             comp.collapsed = true;
117733             comp.fireEvent('collapse', comp);
117734         }
117735
117736         /*
117737          * Set everything to the new positions. Note that we
117738          * only want to animate the collapse if it wasn't configured
117739          * initially with collapsed: true
117740          */
117741         if (comp.animCollapse && me.initialCollapsedComplete) {
117742             shadowLayout.layout();
117743             compEl.dom.style.zIndex = 100;
117744
117745             // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
117746             if (!miniCollapse) {
117747                 placeholder.el.hide();
117748             }
117749             compEl.slideOut(me.slideDirection[comp.region], {
117750                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
117751                 listeners: {
117752                     afteranimate: function() {
117753                         compEl.show().setLeftTop(-10000, -10000);
117754                         compEl.dom.style.zIndex = '';
117755
117756                         // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
117757                        if (!miniCollapse) {
117758                             placeholder.el.slideIn(me.slideDirection[comp.region], {
117759                                 easing: 'linear',
117760                                 duration: 100
117761                             });
117762                         }
117763                         afterCollapse();
117764                     }
117765                 }
117766             });
117767         } else {
117768             compEl.setLeftTop(-10000, -10000);
117769             shadowLayout.layout();
117770             afterCollapse();
117771         }
117772
117773         return false;
117774     },
117775
117776     // Hijack the expand operation to remove the placeholder and slide the region back in.
117777     onBeforeRegionExpand: function(comp, animate) {
117778         this.onPlaceHolderToolClick(null, null, null, {client: comp});
117779         return false;
117780     },
117781
117782     // Called when the collapsed placeholder is clicked to reinstate a "collapsed" (in reality hidden) Panel.
117783     onPlaceHolderToolClick: function(e, target, owner, tool) {
117784         var me = this,
117785             comp = tool.client,
117786
117787             // Hide the placeholder unless it was the Component's preexisting splitter
117788             hidePlaceholder = (comp.collapseMode != 'mini') || !comp.split,
117789             compEl = comp.el,
117790             toCompBox,
117791             placeholder = comp.placeholder,
117792             placeholderEl = placeholder.el,
117793             shadowContainer = comp.shadowOwnerCt,
117794             shadowLayout = shadowContainer.layout,
117795             curSize,
117796             sl = me.owner.suspendLayout,
117797             scsl = shadowContainer.suspendLayout,
117798             isFloating;
117799
117800         // If the slide in is still going, stop it.
117801         // This will either leave the Component in its fully floated state (which is processed below)
117802         // or in its collapsed state. Either way, we expand it..
117803         if (comp.getActiveAnimation()) {
117804             comp.stopAnimation();
117805         }
117806
117807         // If the Component is fully floated when they click the placeholder Tool,
117808         // it will be primed with a slide out animation object... so delete that
117809         // and remove the mouseout listeners
117810         if (comp.slideOutAnim) {
117811             // Remove mouse leave monitors
117812             compEl.un(comp.panelMouseMon);
117813             placeholderEl.un(comp.placeholderMouseMon);
117814
117815             delete comp.slideOutAnim;
117816             delete comp.panelMouseMon;
117817             delete comp.placeholderMouseMon;
117818
117819             // If the Panel was floated and primed with a slideOut animation, we don't want to animate its layout operation.
117820             isFloating = true;
117821         }
117822
117823         // Do not trigger a layout during transition to expanded Component
117824         me.owner.suspendLayout = true;
117825         shadowContainer.suspendLayout = true;
117826
117827         // Prevent upward notifications from downstream layouts
117828         shadowLayout.layoutBusy = true;
117829         if (shadowContainer.componentLayout) {
117830             shadowContainer.componentLayout.layoutBusy = true;
117831         }
117832         me.shadowContainer.layout.layoutBusy = true;
117833         me.layoutBusy = true;
117834         me.owner.componentLayout.layoutBusy = true;
117835
117836         // Unset the hidden and collapsed flags set in onBeforeRegionCollapse. The shadowLayout will now take it into account
117837         // Find where the shadow Box layout plans to put the expanding Component.
117838         comp.hidden = false;
117839         comp.collapsed = false;
117840         if (hidePlaceholder) {
117841             placeholder.hidden = true;
117842         }
117843         toCompBox = shadowLayout.calculateChildBox(comp);
117844
117845         // Show the collapse tool in case it was hidden by the slide-in
117846         if (comp.collapseTool) {
117847             comp.collapseTool.show();
117848         }
117849
117850         // If we're going to animate, we need to hide the component before moving it back into position
117851         if (comp.animCollapse && !isFloating) {
117852             compEl.setStyle('visibility', 'hidden');
117853         }
117854         compEl.setLeftTop(toCompBox.left, toCompBox.top);
117855
117856         // Equalize the size of the expanding Component prior to animation
117857         // in case the layout area has changed size during the time it was collapsed.
117858         curSize = comp.getSize();
117859         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
117860             me.setItemSize(comp, toCompBox.width, toCompBox.height);
117861         }
117862
117863         // Jobs to be done after the expand has been done
117864         function afterExpand() {
117865             // Reinstate automatic laying out.
117866             me.owner.suspendLayout = sl;
117867             shadowContainer.suspendLayout = scsl;
117868             delete shadowLayout.layoutBusy;
117869             if (shadowContainer.componentLayout) {
117870                 delete shadowContainer.componentLayout.layoutBusy;
117871             }
117872             delete me.shadowContainer.layout.layoutBusy;
117873             delete me.layoutBusy;
117874             delete me.owner.componentLayout.layoutBusy;
117875
117876             // In case it was floated out and they clicked the re-expand tool
117877             comp.removeCls(Ext.baseCSSPrefix + 'border-region-slide-in');
117878
117879             // Fire the expand event: The Panel has in fact been expanded, but by removal of an alternative Component
117880             comp.fireEvent('expand', comp);
117881         }
117882
117883         // Hide the placeholder
117884         if (hidePlaceholder) {
117885             placeholder.el.hide();
117886         }
117887
117888         // Slide the expanding Component to its new position.
117889         // When that is done, layout the layout.
117890         if (comp.animCollapse && !isFloating) {
117891             compEl.dom.style.zIndex = 100;
117892             compEl.slideIn(me.slideDirection[comp.region], {
117893                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
117894                 listeners: {
117895                     afteranimate: function() {
117896                         compEl.dom.style.zIndex = '';
117897                         comp.hidden = false;
117898                         shadowLayout.onLayout();
117899                         afterExpand();
117900                     }
117901                 }
117902             });
117903         } else {
117904             shadowLayout.onLayout();
117905             afterExpand();
117906         }
117907     },
117908
117909     floatCollapsedPanel: function(e, comp) {
117910
117911         if (comp.floatable === false) {
117912             return;
117913         }
117914
117915         var me = this,
117916             compEl = comp.el,
117917             placeholder = comp.placeholder,
117918             placeholderEl = placeholder.el,
117919             shadowContainer = comp.shadowOwnerCt,
117920             shadowLayout = shadowContainer.layout,
117921             placeholderBox = shadowLayout.getChildBox(placeholder),
117922             scsl = shadowContainer.suspendLayout,
117923             curSize, toCompBox, compAnim;
117924
117925         // Ignore clicks on tools.
117926         if (e.getTarget('.' + Ext.baseCSSPrefix + 'tool')) {
117927             return;
117928         }
117929
117930         // It's *being* animated, ignore the click.
117931         // Possible future enhancement: Stop and *reverse* the current active Fx.
117932         if (compEl.getActiveAnimation()) {
117933             return;
117934         }
117935
117936         // If the Component is already fully floated when they click the placeholder,
117937         // it will be primed with a slide out animation object... so slide it out.
117938         if (comp.slideOutAnim) {
117939             me.slideOutFloatedComponent(comp);
117940             return;
117941         }
117942
117943         // Function to be called when the mouse leaves the floated Panel
117944         // Slide out when the mouse leaves the region bounded by the slid Component and its placeholder.
117945         function onMouseLeaveFloated(e) {
117946             var slideRegion = compEl.getRegion().union(placeholderEl.getRegion()).adjust(1, -1, -1, 1);
117947
117948             // If mouse is not within slide Region, slide it out
117949             if (!slideRegion.contains(e.getPoint())) {
117950                 me.slideOutFloatedComponent(comp);
117951             }
117952         }
117953
117954         // Monitor for mouseouting of the placeholder. Hide it if they exit for half a second or more
117955         comp.placeholderMouseMon = placeholderEl.monitorMouseLeave(500, onMouseLeaveFloated);
117956
117957         // Do not trigger a layout during slide out of the Component
117958         shadowContainer.suspendLayout = true;
117959
117960         // Prevent upward notifications from downstream layouts
117961         me.layoutBusy = true;
117962         me.owner.componentLayout.layoutBusy = true;
117963
117964         // The collapse tool is hidden while slid.
117965         // It is re-shown on expand.
117966         if (comp.collapseTool) {
117967             comp.collapseTool.hide();
117968         }
117969
117970         // Set flags so that the layout will calculate the boxes for what we want
117971         comp.hidden = false;
117972         comp.collapsed = false;
117973         placeholder.hidden = true;
117974
117975         // Recalculate new arrangement of the Component being floated.
117976         toCompBox = shadowLayout.calculateChildBox(comp);
117977         placeholder.hidden = false;
117978
117979         // Component to appear just after the placeholder, whatever "after" means in the context of the shadow Box layout.
117980         if (comp.region == 'north' || comp.region == 'west') {
117981             toCompBox[shadowLayout.parallelBefore] += placeholderBox[shadowLayout.parallelPrefix] - 1;
117982         } else {
117983             toCompBox[shadowLayout.parallelBefore] -= (placeholderBox[shadowLayout.parallelPrefix] - 1);
117984         }
117985         compEl.setStyle('visibility', 'hidden');
117986         compEl.setLeftTop(toCompBox.left, toCompBox.top);
117987
117988         // Equalize the size of the expanding Component prior to animation
117989         // in case the layout area has changed size during the time it was collapsed.
117990         curSize = comp.getSize();
117991         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
117992             me.setItemSize(comp, toCompBox.width, toCompBox.height);
117993         }
117994
117995         // This animation slides the collapsed Component's el out to just beyond its placeholder
117996         compAnim = {
117997             listeners: {
117998                 afteranimate: function() {
117999                     shadowContainer.suspendLayout = scsl;
118000                     delete me.layoutBusy;
118001                     delete me.owner.componentLayout.layoutBusy;
118002
118003                     // Prime the Component with an Anim config object to slide it back out
118004                     compAnim.listeners = {
118005                         afterAnimate: function() {
118006                             compEl.show().removeCls(Ext.baseCSSPrefix + 'border-region-slide-in').setLeftTop(-10000, -10000);
118007
118008                             // Reinstate the correct, current state after slide out animation finishes
118009                             comp.hidden = true;
118010                             comp.collapsed = true;
118011                             delete comp.slideOutAnim;
118012                             delete comp.panelMouseMon;
118013                             delete comp.placeholderMouseMon;
118014                         }
118015                     };
118016                     comp.slideOutAnim = compAnim;
118017                 }
118018             },
118019             duration: 500
118020         };
118021
118022         // Give the element the correct class which places it at a high z-index
118023         compEl.addCls(Ext.baseCSSPrefix + 'border-region-slide-in');
118024
118025         // Begin the slide in
118026         compEl.slideIn(me.slideDirection[comp.region], compAnim);
118027
118028         // Monitor for mouseouting of the slid area. Hide it if they exit for half a second or more
118029         comp.panelMouseMon = compEl.monitorMouseLeave(500, onMouseLeaveFloated);
118030
118031     },
118032
118033     slideOutFloatedComponent: function(comp) {
118034         var compEl = comp.el,
118035             slideOutAnim;
118036
118037         // Remove mouse leave monitors
118038         compEl.un(comp.panelMouseMon);
118039         comp.placeholder.el.un(comp.placeholderMouseMon);
118040
118041         // Slide the Component out
118042         compEl.slideOut(this.slideDirection[comp.region], comp.slideOutAnim);
118043
118044         delete comp.slideOutAnim;
118045         delete comp.panelMouseMon;
118046         delete comp.placeholderMouseMon;
118047     },
118048
118049     /*
118050      * @private
118051      * Ensure any collapsed placeholder Component is destroyed along with its region.
118052      * Can't do this in onDestroy because they may remove a Component and use it elsewhere.
118053      */
118054     onRegionDestroy: function(comp) {
118055         var placeholder = comp.placeholder;
118056         if (placeholder) {
118057             delete placeholder.ownerCt;
118058             placeholder.destroy();
118059         }
118060     },
118061
118062     /*
118063      * @private
118064      * Ensure any shadow Containers are destroyed.
118065      * Ensure we don't keep references to Components.
118066      */
118067     onDestroy: function() {
118068         var me = this,
118069             shadowContainer = me.shadowContainer,
118070             embeddedContainer = me.embeddedContainer;
118071
118072         if (shadowContainer) {
118073             delete shadowContainer.ownerCt;
118074             Ext.destroy(shadowContainer);
118075         }
118076
118077         if (embeddedContainer) {
118078             delete embeddedContainer.ownerCt;
118079             Ext.destroy(embeddedContainer);
118080         }
118081         delete me.regions;
118082         delete me.splitters;
118083         delete me.shadowContainer;
118084         delete me.embeddedContainer;
118085         me.callParent(arguments);
118086     }
118087 });
118088
118089 /**
118090  * @class Ext.layout.container.Card
118091  * @extends Ext.layout.container.AbstractCard
118092   * <p>This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
118093   * visible at any given time.  This layout style is most commonly used for wizards, tab implementations, etc.
118094   * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,
118095   * and should generally not need to be created directly via the new keyword.</p>
118096   * <p>The CardLayout's focal method is {@link #setActiveItem}.  Since only one panel is displayed at a time,
118097   * the only way to move from one Component to the next is by calling setActiveItem, passing the id or index of
118098   * the next panel to display.  The layout itself does not provide a user interface for handling this navigation,
118099   * so that functionality must be provided by the developer.</p>
118100   * <p>In the following example, a simplistic wizard setup is demonstrated.  A button bar is added
118101   * to the footer of the containing panel to provide navigation buttons.  The buttons will be handled by a
118102   * common navigation routine -- for this example, the implementation of that routine has been ommitted since
118103   * it can be any type of custom logic.  Note that other uses of a CardLayout (like a tab control) would require a
118104   * completely different implementation.  For serious implementations, a better approach would be to extend
118105   * CardLayout to provide the custom functionality needed.  
118106   * {@img Ext.layout.container.Card/Ext.layout.container.Card.png Ext.layout.container.Card container layout}
118107   * Example usage:</p>
118108   * <pre><code>
118109     var navHandler = function(direction){
118110          // This routine could contain business logic required to manage the navigation steps.
118111          // It would call setActiveItem as needed, manage navigation button state, handle any
118112          // branching logic that might be required, handle alternate actions like cancellation
118113          // or finalization, etc.  A complete wizard implementation could get pretty
118114          // sophisticated depending on the complexity required, and should probably be
118115          // done as a subclass of CardLayout in a real-world implementation.
118116      };
118117
118118     Ext.create('Ext.panel.Panel', {
118119         title: 'Example Wizard',
118120         width: 300,
118121         height: 200,
118122         layout: 'card',
118123         activeItem: 0, // make sure the active item is set on the container config!
118124         bodyStyle: 'padding:15px',
118125         defaults: {
118126             // applied to each contained panel
118127             border:false
118128         },
118129         // just an example of one possible navigation scheme, using buttons
118130         bbar: [
118131         {
118132             id: 'move-prev',
118133             text: 'Back',
118134             handler: navHandler(this, [-1]),
118135             disabled: true
118136         },
118137         '->', // greedy spacer so that the buttons are aligned to each side
118138         {
118139             id: 'move-next',
118140             text: 'Next',
118141             handler: navHandler(this, [1])
118142         }],
118143         // the panels (or "cards") within the layout
118144         items: [{
118145             id: 'card-0',
118146             html: '&lt;h1&gt;Welcome to the Wizard!&lt;/h1&gt;&lt;p&gt;Step 1 of 3&lt;/p&gt;'
118147         },{
118148             id: 'card-1',
118149             html: '&lt;p&gt;Step 2 of 3&lt;/p&gt;'
118150         },{
118151             id: 'card-2',
118152             html: '&lt;h1&gt;Congratulations!&lt;/h1&gt;&lt;p&gt;Step 3 of 3 - Complete&lt;/p&gt;'
118153         }],
118154         renderTo: Ext.getBody()
118155     });  
118156  </code></pre>
118157   */
118158 Ext.define('Ext.layout.container.Card', {
118159
118160     /* Begin Definitions */
118161
118162     alias: ['layout.card'],
118163     alternateClassName: 'Ext.layout.CardLayout',
118164
118165     extend: 'Ext.layout.container.AbstractCard',
118166
118167     /* End Definitions */
118168
118169     setActiveItem: function(newCard) {
118170         var me = this,
118171             owner = me.owner,
118172             oldCard = me.activeItem,
118173             newIndex;
118174
118175         // Block upward layouts until we are done.
118176         me.layoutBusy = true;
118177
118178         newCard = me.parseActiveItem(newCard);
118179         newIndex = owner.items.indexOf(newCard);
118180
118181         // If the card is not a child of the owner, then add it
118182         if (newIndex == -1) {
118183             newIndex = owner.items.items.length;
118184             owner.add(newCard);
118185         }
118186
118187         // Is this a valid, different card?
118188         if (newCard && oldCard != newCard) {
118189             // If the card has not been rendered yet, now is the time to do so.
118190             if (!newCard.rendered) {
118191                 me.renderItem(newCard, me.getRenderTarget(), owner.items.length);
118192                 me.configureItem(newCard, 0);
118193             }
118194
118195             me.activeItem = newCard;
118196
118197             // Fire the beforeactivate and beforedeactivate events on the cards
118198             if (newCard.fireEvent('beforeactivate', newCard, oldCard) === false) {
118199                 me.layoutBusy = false;
118200                 return false;
118201             }
118202             if (oldCard && oldCard.fireEvent('beforedeactivate', oldCard, newCard) === false) {
118203                 me.layoutBusy = false;
118204                 return false;
118205             }
118206
118207             // If the card hasnt been sized yet, do it now
118208             if (!me.sizeAllCards) {
118209                 me.setItemBox(newCard, me.getTargetBox());
118210             }
118211             else {
118212                 // onLayout calls setItemBox
118213                 me.onLayout();
118214             }
118215
118216             if (oldCard) {
118217                 if (me.hideInactive) {
118218                     oldCard.hide();
118219                 }
118220                 oldCard.fireEvent('deactivate', oldCard, newCard);
118221             }
118222
118223             // Make sure the new card is shown
118224             if (newCard.hidden) {
118225                 newCard.show();
118226             }
118227
118228             newCard.fireEvent('activate', newCard, oldCard);
118229
118230             me.layoutBusy = false;
118231
118232             if (!me.sizeAllCards) {
118233                 if (!owner.componentLayout.layoutBusy) {
118234                     me.onLayout();
118235                 }
118236             }
118237             return newCard;
118238         }
118239
118240         me.layoutBusy = false;
118241         return false;
118242     }
118243 });
118244 /**
118245  * @class Ext.layout.container.Column
118246  * @extends Ext.layout.container.Auto
118247  * <p>This is the layout style of choice for creating structural layouts in a multi-column format where the width of
118248  * each column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content.
118249  * This class is intended to be extended or created via the layout:'column' {@link Ext.container.Container#layout} config,
118250  * and should generally not need to be created directly via the new keyword.</p>
118251  * <p>ColumnLayout does not have any direct config options (other than inherited ones), but it does support a
118252  * specific config property of <b><tt>columnWidth</tt></b> that can be included in the config of any panel added to it.  The
118253  * layout will use the columnWidth (if present) or width of each panel during layout to determine how to size each panel.
118254  * If width or columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).</p>
118255  * <p>The width property is always evaluated as pixels, and must be a number greater than or equal to 1.
118256  * The columnWidth property is always evaluated as a percentage, and must be a decimal value greater than 0 and
118257  * less than 1 (e.g., .25).</p>
118258  * <p>The basic rules for specifying column widths are pretty simple.  The logic makes two passes through the
118259  * set of contained panels.  During the first layout pass, all panels that either have a fixed width or none
118260  * specified (auto) are skipped, but their widths are subtracted from the overall container width.  During the second
118261  * pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages based on
118262  * the total <b>remaining</b> container width.  In other words, percentage width panels are designed to fill the space
118263  * left over by all the fixed-width and/or auto-width panels.  Because of this, while you can specify any number of columns
118264  * with different percentages, the columnWidths must always add up to 1 (or 100%) when added together, otherwise your
118265  * layout may not render as expected.  
118266  * {@img Ext.layout.container.Column/Ext.layout.container.Column1.png Ext.layout.container.Column container layout}
118267  * Example usage:</p>
118268  * <pre><code>
118269     // All columns are percentages -- they must add up to 1
118270     Ext.create('Ext.panel.Panel', {
118271         title: 'Column Layout - Percentage Only',
118272         width: 350,
118273         height: 250,
118274         layout:'column',
118275         items: [{
118276             title: 'Column 1',
118277             columnWidth: .25
118278         },{
118279             title: 'Column 2',
118280             columnWidth: .55
118281         },{
118282             title: 'Column 3',
118283             columnWidth: .20
118284         }],
118285         renderTo: Ext.getBody()
118286     }); 
118287
118288 // {@img Ext.layout.container.Column/Ext.layout.container.Column2.png Ext.layout.container.Column container layout}
118289 // Mix of width and columnWidth -- all columnWidth values must add up
118290 // to 1. The first column will take up exactly 120px, and the last two
118291 // columns will fill the remaining container width.
118292
118293     Ext.create('Ext.Panel', {
118294         title: 'Column Layout - Mixed',
118295         width: 350,
118296         height: 250,
118297         layout:'column',
118298         items: [{
118299             title: 'Column 1',
118300             width: 120
118301         },{
118302             title: 'Column 2',
118303             columnWidth: .7
118304         },{
118305             title: 'Column 3',
118306             columnWidth: .3
118307         }],
118308         renderTo: Ext.getBody()
118309     }); 
118310 </code></pre>
118311  */
118312 Ext.define('Ext.layout.container.Column', {
118313
118314     extend: 'Ext.layout.container.Auto',
118315     alias: ['layout.column'],
118316     alternateClassName: 'Ext.layout.ColumnLayout',
118317
118318     type: 'column',
118319
118320     itemCls: Ext.baseCSSPrefix + 'column',
118321
118322     targetCls: Ext.baseCSSPrefix + 'column-layout-ct',
118323
118324     scrollOffset: 0,
118325
118326     bindToOwnerCtComponent: false,
118327
118328     getRenderTarget : function() {
118329         if (!this.innerCt) {
118330
118331             // the innerCt prevents wrapping and shuffling while
118332             // the container is resizing
118333             this.innerCt = this.getTarget().createChild({
118334                 cls: Ext.baseCSSPrefix + 'column-inner'
118335             });
118336
118337             // Column layout uses natural HTML flow to arrange the child items.
118338             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
118339             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
118340             this.clearEl = this.innerCt.createChild({
118341                 cls: Ext.baseCSSPrefix + 'clear',
118342                 role: 'presentation'
118343             });
118344         }
118345         return this.innerCt;
118346     },
118347
118348     // private
118349     onLayout : function() {
118350         var me = this,
118351             target = me.getTarget(),
118352             items = me.getLayoutItems(),
118353             len = items.length,
118354             item,
118355             i,
118356             parallelMargins = [],
118357             itemParallelMargins,
118358             size,
118359             availableWidth,
118360             columnWidth;
118361
118362         size = me.getLayoutTargetSize();
118363         if (size.width < len * 10) { // Don't lay out in impossibly small target (probably display:none, or initial, unsized Container)
118364             return;
118365         }
118366
118367         // On the first pass, for all except IE6-7, we lay out the items with no scrollbars visible using style overflow: hidden.
118368         // If, after the layout, it is detected that there is vertical overflow,
118369         // we will recurse back through here. Do not adjust overflow style at that time.
118370         if (me.adjustmentPass) {
118371             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
118372                 size.width = me.adjustedWidth;
118373             }
118374         } else {
118375             i = target.getStyle('overflow');
118376             if (i && i != 'hidden') {
118377                 me.autoScroll = true;
118378                 if (!(Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks)) {
118379                     target.setStyle('overflow', 'hidden');
118380                     size = me.getLayoutTargetSize();
118381                 }
118382             }
118383         }
118384
118385         availableWidth = size.width - me.scrollOffset;
118386         me.innerCt.setWidth(availableWidth);
118387
118388         // some columns can be percentages while others are fixed
118389         // so we need to make 2 passes
118390         for (i = 0; i < len; i++) {
118391             item = items[i];
118392             itemParallelMargins = parallelMargins[i] = item.getEl().getMargin('lr');
118393             if (!item.columnWidth) {
118394                 availableWidth -= (item.getWidth() + itemParallelMargins);
118395             }
118396         }
118397
118398         availableWidth = availableWidth < 0 ? 0 : availableWidth;
118399         for (i = 0; i < len; i++) {
118400             item = items[i];
118401             if (item.columnWidth) {
118402                 columnWidth = Math.floor(item.columnWidth * availableWidth) - parallelMargins[i];
118403                 if (item.getWidth() != columnWidth) {
118404                     me.setItemSize(item, columnWidth, item.height);
118405                 }
118406             }
118407         }
118408
118409         // After the first pass on an autoScroll layout, restore the overflow settings if it had been changed (only changed for non-IE6)
118410         if (!me.adjustmentPass && me.autoScroll) {
118411
118412             // If there's a vertical overflow, relay with scrollbars
118413             target.setStyle('overflow', 'auto');
118414             me.adjustmentPass = (target.dom.scrollHeight > size.height);
118415             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
118416                 me.adjustedWidth = size.width - Ext.getScrollBarWidth();
118417             } else {
118418                 target.setStyle('overflow', 'auto');
118419             }
118420
118421             // If the layout caused height overflow, recurse back and recalculate (with overflow setting restored on non-IE6)
118422             if (me.adjustmentPass) {
118423                 me.onLayout();
118424             }
118425         }
118426         delete me.adjustmentPass;
118427     }
118428 });
118429 /**
118430  * @class Ext.layout.container.Table
118431  * @extends Ext.layout.container.Auto
118432  * <p>This layout allows you to easily render content into an HTML table.  The total number of columns can be
118433  * specified, and rowspan and colspan can be used to create complex layouts within the table.
118434  * This class is intended to be extended or created via the <code>layout: {type: 'table'}</code>
118435  * {@link Ext.container.Container#layout} config, and should generally not need to be created directly via the new keyword.</p>
118436  * <p>Note that when creating a layout via config, the layout-specific config properties must be passed in via
118437  * the {@link Ext.container.Container#layout} object which will then be applied internally to the layout.  In the
118438  * case of TableLayout, the only valid layout config properties are {@link #columns} and {@link #tableAttrs}.
118439  * However, the items added to a TableLayout can supply the following table-specific config properties:</p>
118440  * <ul>
118441  * <li><b>rowspan</b> Applied to the table cell containing the item.</li>
118442  * <li><b>colspan</b> Applied to the table cell containing the item.</li>
118443  * <li><b>cellId</b> An id applied to the table cell containing the item.</li>
118444  * <li><b>cellCls</b> A CSS class name added to the table cell containing the item.</li>
118445  * </ul>
118446  * <p>The basic concept of building up a TableLayout is conceptually very similar to building up a standard
118447  * HTML table.  You simply add each panel (or "cell") that you want to include along with any span attributes
118448  * specified as the special config properties of rowspan and colspan which work exactly like their HTML counterparts.
118449  * Rather than explicitly creating and nesting rows and columns as you would in HTML, you simply specify the
118450  * total column count in the layoutConfig and start adding panels in their natural order from left to right,
118451  * top to bottom.  The layout will automatically figure out, based on the column count, rowspans and colspans,
118452  * how to position each panel within the table.  Just like with HTML tables, your rowspans and colspans must add
118453  * up correctly in your overall layout or you'll end up with missing and/or extra cells!  Example usage:</p>
118454  * {@img Ext.layout.container.Table/Ext.layout.container.Table.png Ext.layout.container.Table container layout}
118455  * <pre><code>
118456 // This code will generate a layout table that is 3 columns by 2 rows
118457 // with some spanning included.  The basic layout will be:
118458 // +--------+-----------------+
118459 // |   A    |   B             |
118460 // |        |--------+--------|
118461 // |        |   C    |   D    |
118462 // +--------+--------+--------+
118463     Ext.create('Ext.panel.Panel', {
118464         title: 'Table Layout',
118465         width: 300,
118466         height: 150,
118467         layout: {
118468             type: 'table',
118469             // The total column count must be specified here
118470             columns: 3
118471         },
118472         defaults: {
118473             // applied to each contained panel
118474             bodyStyle:'padding:20px'
118475         },
118476         items: [{
118477             html: 'Cell A content',
118478             rowspan: 2
118479         },{
118480             html: 'Cell B content',
118481             colspan: 2
118482         },{
118483             html: 'Cell C content',
118484             cellCls: 'highlight'
118485         },{
118486             html: 'Cell D content'
118487         }],
118488         renderTo: Ext.getBody()
118489     });
118490 </code></pre>
118491  */
118492
118493 Ext.define('Ext.layout.container.Table', {
118494
118495     /* Begin Definitions */
118496
118497     alias: ['layout.table'],
118498     extend: 'Ext.layout.container.Auto',
118499     alternateClassName: 'Ext.layout.TableLayout',
118500
118501     /* End Definitions */
118502
118503     /**
118504      * @cfg {Number} columns
118505      * The total number of columns to create in the table for this layout.  If not specified, all Components added to
118506      * this layout will be rendered into a single row using one column per Component.
118507      */
118508
118509     // private
118510     monitorResize:false,
118511
118512     type: 'table',
118513
118514     // Table layout is a self-sizing layout. When an item of for example, a dock layout, the Panel must expand to accommodate
118515     // a table layout. See in particular AbstractDock::onLayout for use of this flag.
118516     autoSize: true,
118517
118518     clearEl: true, // Base class will not create it if already truthy. Not needed in tables.
118519
118520     targetCls: Ext.baseCSSPrefix + 'table-layout-ct',
118521     tableCls: Ext.baseCSSPrefix + 'table-layout',
118522     cellCls: Ext.baseCSSPrefix + 'table-layout-cell',
118523
118524     /**
118525      * @cfg {Object} tableAttrs
118526      * <p>An object containing properties which are added to the {@link Ext.core.DomHelper DomHelper} specification
118527      * used to create the layout's <tt>&lt;table&gt;</tt> element. Example:</p><pre><code>
118528 {
118529     xtype: 'panel',
118530     layout: {
118531         type: 'table',
118532         columns: 3,
118533         tableAttrs: {
118534             style: {
118535                 width: '100%'
118536             }
118537         }
118538     }
118539 }</code></pre>
118540      */
118541     tableAttrs:null,
118542
118543     /**
118544      * @private
118545      * Iterates over all passed items, ensuring they are rendered in a cell in the proper
118546      * location in the table structure.
118547      */
118548     renderItems: function(items) {
118549         var tbody = this.getTable().tBodies[0],
118550             rows = tbody.rows,
118551             i = 0,
118552             len = items.length,
118553             cells, curCell, rowIdx, cellIdx, item, trEl, tdEl, itemCt;
118554
118555         // Calculate the correct cell structure for the current items
118556         cells = this.calculateCells(items);
118557
118558         // Loop over each cell and compare to the current cells in the table, inserting/
118559         // removing/moving cells as needed, and making sure each item is rendered into
118560         // the correct cell.
118561         for (; i < len; i++) {
118562             curCell = cells[i];
118563             rowIdx = curCell.rowIdx;
118564             cellIdx = curCell.cellIdx;
118565             item = items[i];
118566
118567             // If no row present, create and insert one
118568             trEl = rows[rowIdx];
118569             if (!trEl) {
118570                 trEl = tbody.insertRow(rowIdx);
118571             }
118572
118573             // If no cell present, create and insert one
118574             itemCt = tdEl = Ext.get(trEl.cells[cellIdx] || trEl.insertCell(cellIdx));
118575             if (this.needsDivWrap()) { //create wrapper div if needed - see docs below
118576                 itemCt = tdEl.first() || tdEl.createChild({tag: 'div'});
118577                 itemCt.setWidth(null);
118578             }
118579
118580             // Render or move the component into the cell
118581             if (!item.rendered) {
118582                 this.renderItem(item, itemCt, 0);
118583             }
118584             else if (!this.isValidParent(item, itemCt, 0)) {
118585                 this.moveItem(item, itemCt, 0);
118586             }
118587
118588             // Set the cell properties
118589             tdEl.set({
118590                 colSpan: item.colspan || 1,
118591                 rowSpan: item.rowspan || 1,
118592                 id: item.cellId || '',
118593                 cls: this.cellCls + ' ' + (item.cellCls || '')
118594             });
118595
118596             // If at the end of a row, remove any extra cells
118597             if (!cells[i + 1] || cells[i + 1].rowIdx !== rowIdx) {
118598                 cellIdx++;
118599                 while (trEl.cells[cellIdx]) {
118600                     trEl.deleteCell(cellIdx);
118601                 }
118602             }
118603         }
118604
118605         // Delete any extra rows
118606         rowIdx++;
118607         while (tbody.rows[rowIdx]) {
118608             tbody.deleteRow(rowIdx);
118609         }
118610     },
118611
118612     afterLayout: function() {
118613         this.callParent();
118614
118615         if (this.needsDivWrap()) {
118616             // set wrapper div width to match layed out item - see docs below
118617             Ext.Array.forEach(this.getLayoutItems(), function(item) {
118618                 Ext.fly(item.el.dom.parentNode).setWidth(item.getWidth());
118619             });
118620         }
118621     },
118622
118623     /**
118624      * @private
118625      * Determine the row and cell indexes for each component, taking into consideration
118626      * the number of columns and each item's configured colspan/rowspan values.
118627      * @param {Array} items The layout components
118628      * @return {Array} List of row and cell indexes for each of the components
118629      */
118630     calculateCells: function(items) {
118631         var cells = [],
118632             rowIdx = 0,
118633             colIdx = 0,
118634             cellIdx = 0,
118635             totalCols = this.columns || Infinity,
118636             rowspans = [], //rolling list of active rowspans for each column
118637             i = 0, j,
118638             len = items.length,
118639             item;
118640
118641         for (; i < len; i++) {
118642             item = items[i];
118643
118644             // Find the first available row/col slot not taken up by a spanning cell
118645             while (colIdx >= totalCols || rowspans[colIdx] > 0) {
118646                 if (colIdx >= totalCols) {
118647                     // move down to next row
118648                     colIdx = 0;
118649                     cellIdx = 0;
118650                     rowIdx++;
118651
118652                     // decrement all rowspans
118653                     for (j = 0; j < totalCols; j++) {
118654                         if (rowspans[j] > 0) {
118655                             rowspans[j]--;
118656                         }
118657                     }
118658                 } else {
118659                     colIdx++;
118660                 }
118661             }
118662
118663             // Add the cell info to the list
118664             cells.push({
118665                 rowIdx: rowIdx,
118666                 cellIdx: cellIdx
118667             });
118668
118669             // Increment
118670             rowspans[colIdx] = item.rowspan || 1;
118671             colIdx += item.colspan || 1;
118672             cellIdx++;
118673         }
118674
118675         return cells;
118676     },
118677
118678     /**
118679      * @private
118680      * Return the layout's table element, creating it if necessary.
118681      */
118682     getTable: function() {
118683         var table = this.table;
118684         if (!table) {
118685             table = this.table = this.getTarget().createChild(
118686                 Ext.apply({
118687                     tag: 'table',
118688                     role: 'presentation',
118689                     cls: this.tableCls,
118690                     cellspacing: 0, //TODO should this be specified or should CSS handle it?
118691                     cn: {tag: 'tbody'}
118692                 }, this.tableAttrs),
118693                 null, true
118694             );
118695         }
118696         return table;
118697     },
118698
118699     /**
118700      * @private
118701      * Opera 10.5 has a bug where if a table cell's child has box-sizing:border-box and padding, it
118702      * will include that padding in the size of the cell, making it always larger than the
118703      * shrink-wrapped size of its contents. To get around this we have to wrap the contents in a div
118704      * and then set that div's width to match the item rendered within it afterLayout. This method
118705      * determines whether we need the wrapper div; it currently does a straight UA sniff as this bug
118706      * seems isolated to just Opera 10.5, but feature detection could be added here if needed.
118707      */
118708     needsDivWrap: function() {
118709         return Ext.isOpera10_5;
118710     }
118711 });
118712 /**
118713  * @class Ext.menu.Item
118714  * @extends Ext.Component
118715
118716  * A base class for all menu items that require menu-related functionality such as click handling,
118717  * sub-menus, icons, etc.
118718  * {@img Ext.menu.Menu/Ext.menu.Menu.png Ext.menu.Menu component}
118719 __Example Usage:__
118720     Ext.create('Ext.menu.Menu', {
118721                 width: 100,
118722                 height: 100,
118723                 floating: false,  // usually you want this set to True (default)
118724                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
118725                 items: [{
118726                     text: 'icon item',
118727                     iconCls: 'add16'
118728                 },{
118729                         text: 'text item',
118730                 },{                        
118731                         text: 'plain item',
118732                         plain: true        
118733                 }]
118734         }); 
118735
118736  * @xtype menuitem
118737  * @markdown
118738  * @constructor
118739  * @param {Object} config The config object
118740  */
118741 Ext.define('Ext.menu.Item', {
118742     extend: 'Ext.Component',
118743     alias: 'widget.menuitem',
118744     alternateClassName: 'Ext.menu.TextItem',
118745     
118746     /**
118747      * @property {Boolean} activated
118748      * Whether or not this item is currently activated
118749      */
118750
118751     /**
118752      * @cfg {String} activeCls
118753      * The CSS class added to the menu item when the item is activated (focused/mouseover).
118754      * Defaults to `Ext.baseCSSPrefix + 'menu-item-active'`.
118755      * @markdown
118756      */
118757     activeCls: Ext.baseCSSPrefix + 'menu-item-active',
118758     
118759     /**
118760      * @cfg {String} ariaRole @hide
118761      */
118762     ariaRole: 'menuitem',
118763     
118764     /**
118765      * @cfg {Boolean} canActivate
118766      * Whether or not this menu item can be activated when focused/mouseovered. Defaults to `true`.
118767      * @markdown
118768      */
118769     canActivate: true,
118770     
118771     /**
118772      * @cfg {Number} clickHideDelay
118773      * The delay in milliseconds to wait before hiding the menu after clicking the menu item.
118774      * This only has an effect when `hideOnClick: true`. Defaults to `1`.
118775      * @markdown
118776      */
118777     clickHideDelay: 1,
118778     
118779     /**
118780      * @cfg {Boolean} destroyMenu
118781      * Whether or not to destroy any associated sub-menu when this item is destroyed. Defaults to `true`.
118782      */
118783     destroyMenu: true,
118784     
118785     /**
118786      * @cfg {String} disabledCls
118787      * The CSS class added to the menu item when the item is disabled.
118788      * Defaults to `Ext.baseCSSPrefix + 'menu-item-disabled'`.
118789      * @markdown
118790      */
118791     disabledCls: Ext.baseCSSPrefix + 'menu-item-disabled',
118792     
118793     /**
118794      * @cfg {String} href
118795      * The href attribute to use for the underlying anchor link. Defaults to `#`.
118796      * @markdown
118797      */
118798      
118799      /**
118800       * @cfg {String} hrefTarget
118801       * The target attribute to use for the underlying anchor link. Defaults to `undefined`.
118802       * @markdown
118803       */
118804     
118805     /**
118806      * @cfg {Boolean} hideOnClick
118807      * Whether to not to hide the owning menu when this item is clicked. Defaults to `true`.
118808      * @markdown
118809      */
118810     hideOnClick: true,
118811     
118812     /**
118813      * @cfg {String} icon
118814      * The path to an icon to display in this item. Defaults to `Ext.BLANK_IMAGE_URL`.
118815      * @markdown
118816      */
118817      
118818     /**
118819      * @cfg {String} iconCls
118820      * A CSS class that specifies a `background-image` to use as the icon for this item. Defaults to `undefined`.
118821      * @markdown
118822      */
118823     
118824     isMenuItem: true,
118825     
118826     /**
118827      * @cfg {Mixed} menu
118828      * Either an instance of {@link Ext.menu.Menu} or a config object for an {@link Ext.menu.Menu}
118829      * which will act as a sub-menu to this item.
118830      * @markdown
118831      * @property {Ext.menu.Menu} menu The sub-menu associated with this item, if one was configured.
118832      */
118833     
118834     /**
118835      * @cfg {String} menuAlign
118836      * The default {@link Ext.core.Element#getAlignToXY Ext.Element.getAlignToXY} anchor position value for this
118837      * item's sub-menu relative to this item's position. Defaults to `'tl-tr?'`.
118838      * @markdown
118839      */
118840     menuAlign: 'tl-tr?',
118841     
118842     /**
118843      * @cfg {Number} menuExpandDelay
118844      * The delay in milliseconds before this item's sub-menu expands after this item is moused over. Defaults to `200`.
118845      * @markdown
118846      */
118847     menuExpandDelay: 200,
118848     
118849     /**
118850      * @cfg {Number} menuHideDelay
118851      * The delay in milliseconds before this item's sub-menu hides after this item is moused out. Defaults to `200`.
118852      * @markdown
118853      */
118854     menuHideDelay: 200,
118855     
118856     /**
118857      * @cfg {Boolean} plain
118858      * Whether or not this item is plain text/html with no icon or visual activation. Defaults to `false`.
118859      * @markdown
118860      */
118861     
118862     renderTpl: [
118863         '<tpl if="plain">',
118864             '{text}',
118865         '</tpl>',
118866         '<tpl if="!plain">',
118867             '<a class="' + Ext.baseCSSPrefix + 'menu-item-link" href="{href}" <tpl if="hrefTarget">target="{hrefTarget}"</tpl> hidefocus="true" unselectable="on">',
118868                 '<img src="{icon}" class="' + Ext.baseCSSPrefix + 'menu-item-icon {iconCls}" />',
118869                 '<span class="' + Ext.baseCSSPrefix + 'menu-item-text" <tpl if="menu">style="margin-right: 17px;"</tpl> >{text}</span>',
118870                 '<tpl if="menu">',
118871                     '<img src="' + Ext.BLANK_IMAGE_URL + '" class="' + Ext.baseCSSPrefix + 'menu-item-arrow" />',
118872                 '</tpl>',
118873             '</a>',
118874         '</tpl>'
118875     ],
118876     
118877     maskOnDisable: false,
118878     
118879     /**
118880      * @cfg {String} text
118881      * The text/html to display in this item. Defaults to `undefined`.
118882      * @markdown
118883      */
118884     
118885     activate: function() {
118886         var me = this;
118887         
118888         if (!me.activated && me.canActivate && me.rendered && !me.isDisabled() && me.isVisible()) {
118889             me.el.addCls(me.activeCls);
118890             me.focus();
118891             me.activated = true;
118892             me.fireEvent('activate', me);
118893         }
118894     },
118895     
118896     blur: function() {
118897         this.$focused = false;
118898         this.callParent(arguments);
118899     },
118900     
118901     deactivate: function() {
118902         var me = this;
118903         
118904         if (me.activated) {
118905             me.el.removeCls(me.activeCls);
118906             me.blur();
118907             me.hideMenu();
118908             me.activated = false;
118909             me.fireEvent('deactivate', me);
118910         }
118911     },
118912     
118913     deferExpandMenu: function() {
118914         var me = this;
118915         
118916         if (!me.menu.rendered || !me.menu.isVisible()) {
118917             me.parentMenu.activeChild = me.menu;
118918             me.menu.parentItem = me;
118919             me.menu.parentMenu = me.menu.ownerCt = me.parentMenu;
118920             me.menu.showBy(me, me.menuAlign);
118921         }
118922     },
118923     
118924     deferHideMenu: function() {
118925         if (this.menu.isVisible()) {
118926             this.menu.hide();
118927         }
118928     },
118929     
118930     deferHideParentMenus: function() {
118931         Ext.menu.Manager.hideAll();
118932     },
118933     
118934     expandMenu: function(delay) {
118935         var me = this;
118936         
118937         if (me.menu) {
118938             clearTimeout(me.hideMenuTimer);
118939             if (delay === 0) {
118940                 me.deferExpandMenu();
118941             } else {
118942                 me.expandMenuTimer = Ext.defer(me.deferExpandMenu, Ext.isNumber(delay) ? delay : me.menuExpandDelay, me);
118943             }
118944         }
118945     },
118946     
118947     focus: function() {
118948         this.$focused = true;
118949         this.callParent(arguments);
118950     },
118951     
118952     getRefItems: function(deep){
118953         var menu = this.menu,
118954             items;
118955         
118956         if (menu) {
118957             items = menu.getRefItems(deep);
118958             items.unshift(menu);
118959         }   
118960         return items || [];   
118961     },
118962     
118963     hideMenu: function(delay) {
118964         var me = this;
118965         
118966         if (me.menu) {
118967             clearTimeout(me.expandMenuTimer);
118968             me.hideMenuTimer = Ext.defer(me.deferHideMenu, Ext.isNumber(delay) ? delay : me.menuHideDelay, me);
118969         }
118970     },
118971     
118972     initComponent: function() {
118973         var me = this,
118974             prefix = Ext.baseCSSPrefix,
118975             cls = [prefix + 'menu-item'];
118976         
118977         me.addEvents(
118978             /**
118979              * @event activate
118980              * Fires when this item is activated
118981              * @param {Ext.menu.Item} item The activated item
118982              */
118983             'activate',
118984             
118985             /**
118986              * @event click
118987              * Fires when this item is clicked
118988              * @param {Ext.menu.Item} item The item that was clicked
118989              * @param {Ext.EventObject} e The underyling {@link Ext.EventObject}.
118990              */
118991             'click',
118992             
118993             /**
118994              * @event deactivate
118995              * Fires when this tiem is deactivated
118996              * @param {Ext.menu.Item} item The deactivated item
118997              */
118998             'deactivate'
118999         );
119000         
119001         if (me.plain) {
119002             cls.push(prefix + 'menu-item-plain');
119003         }
119004         
119005         if (me.cls) {
119006             cls.push(me.cls);
119007         }
119008         
119009         me.cls = cls.join(' ');
119010         
119011         if (me.menu) {
119012             me.menu = Ext.menu.Manager.get(me.menu);
119013         }
119014         
119015         me.callParent(arguments);
119016     },
119017     
119018     onClick: function(e) {
119019         var me = this;
119020         
119021         if (!me.href) {
119022             e.stopEvent();
119023         }
119024         
119025         if (me.disabled) {
119026             return;
119027         }
119028         
119029         if (me.hideOnClick) {
119030             me.deferHideParentMenusTimer = Ext.defer(me.deferHideParentMenus, me.clickHideDelay, me);
119031         }
119032         
119033         Ext.callback(me.handler, me.scope || me, [me, e]);
119034         me.fireEvent('click', me, e);
119035         
119036         if (!me.hideOnClick) {
119037             me.focus();
119038         }
119039     },
119040     
119041     onDestroy: function() {
119042         var me = this;
119043         
119044         clearTimeout(me.expandMenuTimer);
119045         clearTimeout(me.hideMenuTimer);
119046         clearTimeout(me.deferHideParentMenusTimer);
119047         
119048         if (me.menu) {
119049             delete me.menu.parentItem;
119050             delete me.menu.parentMenu;
119051             delete me.menu.ownerCt;
119052             if (me.destroyMenu !== false) {
119053                 me.menu.destroy();
119054             }
119055         }
119056         me.callParent(arguments);
119057     },
119058     
119059     onRender: function(ct, pos) {
119060         var me = this,
119061             prefix = '.' + Ext.baseCSSPrefix;
119062         
119063         Ext.applyIf(me.renderData, {
119064             href: me.href || '#',
119065             hrefTarget: me.hrefTarget,
119066             icon: me.icon || Ext.BLANK_IMAGE_URL,
119067             iconCls: me.iconCls,
119068             menu: Ext.isDefined(me.menu),
119069             plain: me.plain,
119070             text: me.text
119071         });
119072         
119073         Ext.applyIf(me.renderSelectors, {
119074             itemEl: prefix + 'menu-item-link',
119075             iconEl: prefix + 'menu-item-icon',
119076             textEl: prefix + 'menu-item-text',
119077             arrowEl: prefix + 'menu-item-arrow'
119078         });
119079         
119080         me.callParent(arguments);
119081     },
119082     
119083     /**
119084      * Sets the {@link #click} handler of this item
119085      * @param {Function} fn The handler function
119086      * @param {Object} scope (optional) The scope of the handler function
119087      */
119088     setHandler: function(fn, scope) {
119089         this.handler = fn || null;
119090         this.scope = scope;
119091     },
119092     
119093     /**
119094      * Sets the {@link #iconCls} of this item
119095      * @param {String} iconCls The CSS class to set to {@link #iconCls}
119096      */
119097     setIconCls: function(iconCls) {
119098         var me = this;
119099         
119100         if (me.iconEl) {
119101             if (me.iconCls) {
119102                 me.iconEl.removeCls(me.iconCls);
119103             }
119104             
119105             if (iconCls) {
119106                 me.iconEl.addCls(iconCls);
119107             }
119108         }
119109         
119110         me.iconCls = iconCls;
119111     },
119112     
119113     /**
119114      * Sets the {@link #text} of this item
119115      * @param {String} text The {@link #text}
119116      */
119117     setText: function(text) {
119118         var me = this,
119119             el = me.textEl || me.el,
119120             newWidth;
119121         
119122         if (text && el) {
119123             el.update(text);
119124                 
119125             if (me.textEl) {
119126                 // Resize the menu to fit the text
119127                 newWidth = me.textEl.getWidth() + me.iconEl.getWidth() + 25 + (me.arrowEl ? me.arrowEl.getWidth() : 0);
119128                 if (newWidth > me.itemEl.getWidth()) {
119129                     me.parentMenu.setWidth(newWidth);
119130                 }
119131             }
119132         } else if (el) {
119133             el.update('');
119134         }
119135         
119136         me.text = text;
119137     }
119138 });
119139
119140 /**
119141  * @class Ext.menu.CheckItem
119142  * @extends Ext.menu.Item
119143
119144 A menu item that contains a togglable checkbox by default, but that can also be a part of a radio group.
119145 {@img Ext.menu.CheckItem/Ext.menu.CheckItem.png Ext.menu.CheckItem component}
119146 __Example Usage__    
119147     Ext.create('Ext.menu.Menu', {
119148                 width: 100,
119149                 height: 110,
119150                 floating: false,  // usually you want this set to True (default)
119151                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
119152                 items: [{
119153                     xtype: 'menucheckitem',
119154                     text: 'select all'
119155                 },{
119156                     xtype: 'menucheckitem',
119157                         text: 'select specific',
119158                 },{
119159             iconCls: 'add16',
119160                     text: 'icon item' 
119161                 },{
119162                     text: 'regular item'
119163                 }]
119164         }); 
119165         
119166  * @xtype menucheckitem
119167  * @markdown
119168  * @constructor
119169  * @param {Object} config The config object
119170  */
119171
119172 Ext.define('Ext.menu.CheckItem', {
119173     extend: 'Ext.menu.Item',
119174     alias: 'widget.menucheckitem',
119175
119176     /**
119177      * @cfg {String} checkedCls
119178      * The CSS class used by {@link #cls} to show the checked state.
119179      * Defaults to `Ext.baseCSSPrefix + 'menu-item-checked'`.
119180      * @markdown
119181      */
119182     checkedCls: Ext.baseCSSPrefix + 'menu-item-checked',
119183     /**
119184      * @cfg {String} uncheckedCls
119185      * The CSS class used by {@link #cls} to show the unchecked state.
119186      * Defaults to `Ext.baseCSSPrefix + 'menu-item-unchecked'`.
119187      * @markdown
119188      */
119189     uncheckedCls: Ext.baseCSSPrefix + 'menu-item-unchecked',
119190     /**
119191      * @cfg {String} groupCls
119192      * The CSS class applied to this item's icon image to denote being a part of a radio group.
119193      * Defaults to `Ext.baseCSSClass + 'menu-group-icon'`.
119194      * Any specified {@link #iconCls} overrides this.
119195      * @markdown
119196      */
119197     groupCls: Ext.baseCSSPrefix + 'menu-group-icon',
119198
119199     /**
119200      * @cfg {Boolean} hideOnClick
119201      * Whether to not to hide the owning menu when this item is clicked.
119202      * Defaults to `false` for checkbox items, and to `true` for radio group items.
119203      * @markdown
119204      */
119205     hideOnClick: false,
119206
119207     afterRender: function() {
119208         var me = this;
119209         this.callParent();
119210         me.checked = !me.checked;
119211         me.setChecked(!me.checked, true);
119212     },
119213
119214     initComponent: function() {
119215         var me = this;
119216         me.addEvents(
119217             /**
119218              * @event beforecheckchange
119219              * Fires before a change event. Return false to cancel.
119220              * @param {Ext.menu.CheckItem} this
119221              * @param {Boolean} checked
119222              */
119223             'beforecheckchange',
119224
119225             /**
119226              * @event checkchange
119227              * Fires after a change event.
119228              * @param {Ext.menu.CheckItem} this
119229              * @param {Boolean} checked
119230              */
119231             'checkchange'
119232         );
119233
119234         me.callParent(arguments);
119235
119236         Ext.menu.Manager.registerCheckable(me);
119237
119238         if (me.group) {
119239             if (!me.iconCls) {
119240                 me.iconCls = me.groupCls;
119241             }
119242             if (me.initialConfig.hideOnClick !== false) {
119243                 me.hideOnClick = true;
119244             }
119245         }
119246     },
119247
119248     /**
119249      * Disables just the checkbox functionality of this menu Item. If this menu item has a submenu, that submenu
119250      * will still be accessible
119251      */
119252     disableCheckChange: function() {
119253         var me = this;
119254
119255         me.iconEl.addCls(me.disabledCls);
119256         me.checkChangeDisabled = true;
119257     },
119258
119259     /**
119260      * Reenables the checkbox functionality of this menu item after having been disabled by {@link #disableCheckChange}
119261      */
119262     enableCheckChange: function() {
119263         var me = this;
119264
119265         me.iconEl.removeCls(me.disabledCls);
119266         me.checkChangeDisabled = false;
119267     },
119268
119269     onClick: function(e) {
119270         var me = this;
119271         if(!me.disabled && !me.checkChangeDisabled && !(me.checked && me.group)) {
119272             me.setChecked(!me.checked);
119273         }
119274         this.callParent([e]);
119275     },
119276
119277     onDestroy: function() {
119278         Ext.menu.Manager.unregisterCheckable(this);
119279         this.callParent(arguments);
119280     },
119281
119282     /**
119283      * Sets the checked state of the item
119284      * @param {Boolean} checked True to check, false to uncheck
119285      * @param {Boolean} suppressEvents (optional) True to prevent firing the checkchange events. Defaults to `false`.
119286      * @markdown
119287      */
119288     setChecked: function(checked, suppressEvents) {
119289         var me = this;
119290         if (me.checked !== checked && (suppressEvents || me.fireEvent('beforecheckchange', me, checked) !== false)) {
119291             if (me.el) {
119292                 me.el[checked  ? 'addCls' : 'removeCls'](me.checkedCls)[!checked ? 'addCls' : 'removeCls'](me.uncheckedCls);
119293             }
119294             me.checked = checked;
119295             Ext.menu.Manager.onCheckChange(me, checked);
119296             if (!suppressEvents) {
119297                 Ext.callback(me.checkHandler, me.scope, [me, checked]);
119298                 me.fireEvent('checkchange', me, checked);
119299             }
119300         }
119301     }
119302 });
119303
119304 /**
119305  * @class Ext.menu.KeyNav
119306  * @private
119307  */
119308 Ext.define('Ext.menu.KeyNav', {
119309     extend: 'Ext.util.KeyNav',
119310
119311     requires: ['Ext.FocusManager'],
119312     
119313     constructor: function(menu) {
119314         var me = this;
119315
119316         me.menu = menu;
119317         me.callParent([menu.el, {
119318             down: me.down,
119319             enter: me.enter,
119320             esc: me.escape,
119321             left: me.left,
119322             right: me.right,
119323             space: me.enter,
119324             tab: me.tab,
119325             up: me.up
119326         }]);
119327     },
119328
119329     down: function(e) {
119330         var me = this,
119331             fi = me.menu.focusedItem;
119332
119333         if (fi && e.getKey() == Ext.EventObject.DOWN && me.isWhitelisted(fi)) {
119334             return true;
119335         }
119336         me.focusNextItem(1);
119337     },
119338
119339     enter: function(e) {
119340         var menu = this.menu;
119341
119342         if (menu.activeItem) {
119343             menu.onClick(e);
119344         }
119345     },
119346
119347     escape: function(e) {
119348         Ext.menu.Manager.hideAll();
119349     },
119350
119351     focusNextItem: function(step) {
119352         var menu = this.menu,
119353             items = menu.items,
119354             focusedItem = menu.focusedItem,
119355             startIdx = focusedItem ? items.indexOf(focusedItem) : -1,
119356             idx = startIdx + step;
119357
119358         while (idx != startIdx) {
119359             if (idx < 0) {
119360                 idx = items.length - 1;
119361             } else if (idx >= items.length) {
119362                 idx = 0;
119363             }
119364
119365             var item = items.getAt(idx);
119366             if (menu.canActivateItem(item)) {
119367                 menu.setActiveItem(item);
119368                 break;
119369             }
119370             idx += step;
119371         }
119372     },
119373
119374     isWhitelisted: function(item) {
119375         return Ext.FocusManager.isWhitelisted(item);
119376     },
119377
119378     left: function(e) {
119379         var menu = this.menu,
119380             fi = menu.focusedItem,
119381             ai = menu.activeItem;
119382
119383         if (fi && this.isWhitelisted(fi)) {
119384             return true;
119385         }
119386
119387         menu.hide();
119388         if (menu.parentMenu) {
119389             menu.parentMenu.focus();
119390         }
119391     },
119392
119393     right: function(e) {
119394         var menu = this.menu,
119395             fi = menu.focusedItem,
119396             ai = menu.activeItem,
119397             am;
119398
119399         if (fi && this.isWhitelisted(fi)) {
119400             return true;
119401         }
119402
119403         if (ai) {
119404             am = menu.activeItem.menu;
119405             if (am) {
119406                 ai.expandMenu(0);
119407                 Ext.defer(function() {
119408                     am.setActiveItem(am.items.getAt(0));
119409                 }, 25);
119410             }
119411         }
119412     },
119413
119414     tab: function(e) {
119415         var me = this;
119416
119417         if (e.shiftKey) {
119418             me.up(e);
119419         } else {
119420             me.down(e);
119421         }
119422     },
119423
119424     up: function(e) {
119425         var me = this,
119426             fi = me.menu.focusedItem;
119427
119428         if (fi && e.getKey() == Ext.EventObject.UP && me.isWhitelisted(fi)) {
119429             return true;
119430         }
119431         me.focusNextItem(-1);
119432     }
119433 });
119434 /**
119435  * @class Ext.menu.Separator
119436  * @extends Ext.menu.Item
119437  *
119438  * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
119439  * add one of these by using "-" in your call to add() or in your items config rather than creating one directly.
119440  *
119441  * {@img Ext.menu.Separator/Ext.menu.Separator.png Ext.menu.Separator component}
119442  *
119443  * ## Code 
119444  *
119445  *     Ext.create('Ext.menu.Menu', {
119446  *         width: 100,
119447  *         height: 100,
119448  *         floating: false,  // usually you want this set to True (default)
119449  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
119450  *         items: [{
119451  *             text: 'icon item',
119452  *             iconCls: 'add16'
119453  *         },{
119454  *             xtype: 'menuseparator'
119455  *         },{
119456  *            text: 'seperator above',
119457  *         },{
119458  *            text: 'regular item',
119459  *         }]
119460  *     }); 
119461  *
119462  * @xtype menuseparator
119463  * @markdown
119464  * @constructor
119465  * @param {Object} config The config object
119466  */
119467 Ext.define('Ext.menu.Separator', {
119468     extend: 'Ext.menu.Item',
119469     alias: 'widget.menuseparator',
119470     
119471     /**
119472      * @cfg {String} activeCls @hide
119473      */
119474     
119475     /**
119476      * @cfg {Boolean} canActivate @hide
119477      */
119478     canActivate: false,
119479     
119480     /**
119481      * @cfg {Boolean} clickHideDelay @hide
119482      */
119483      
119484     /**
119485      * @cfg {Boolean} destroyMenu @hide
119486      */
119487      
119488     /**
119489      * @cfg {Boolean} disabledCls @hide
119490      */
119491      
119492     focusable: false,
119493      
119494     /**
119495      * @cfg {String} href @hide
119496      */
119497     
119498     /**
119499      * @cfg {String} hrefTarget @hide
119500      */
119501     
119502     /**
119503      * @cfg {Boolean} hideOnClick @hide
119504      */
119505     hideOnClick: false,
119506     
119507     /**
119508      * @cfg {String} icon @hide
119509      */
119510     
119511     /**
119512      * @cfg {String} iconCls @hide
119513      */
119514     
119515     /**
119516      * @cfg {Mixed} menu @hide
119517      */
119518     
119519     /**
119520      * @cfg {String} menuAlign @hide
119521      */
119522     
119523     /**
119524      * @cfg {Number} menuExpandDelay @hide
119525      */
119526     
119527     /**
119528      * @cfg {Number} menuHideDelay @hide
119529      */
119530     
119531     /**
119532      * @cfg {Boolean} plain @hide
119533      */
119534     plain: true,
119535     
119536     /**
119537      * @cfg {String} separatorCls
119538      * The CSS class used by the separator item to show the incised line.
119539      * Defaults to `Ext.baseCSSPrefix + 'menu-item-separator'`.
119540      * @markdown
119541      */
119542     separatorCls: Ext.baseCSSPrefix + 'menu-item-separator',
119543     
119544     /**
119545      * @cfg {String} text @hide
119546      */
119547     text: '&#160;',
119548     
119549     onRender: function(ct, pos) {
119550         var me = this,
119551             sepCls = me.separatorCls;
119552             
119553         me.cls += ' ' + sepCls;
119554         
119555         Ext.applyIf(me.renderSelectors, {
119556             itemSepEl: '.' + sepCls
119557         });
119558         
119559         me.callParent(arguments);
119560     }
119561 });
119562 /**
119563  * @class Ext.menu.Menu
119564  * @extends Ext.panel.Panel
119565  *
119566  * A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}.
119567  *
119568  * Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}.
119569  * Menus may also contain {@link Ext.panel.AbstractPanel#dockedItems docked items} because it extends {@link Ext.panel.Panel}.
119570  *
119571  * To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items},
119572  * specify `{@link Ext.menu.Item#iconCls iconCls}: 'no-icon'` _or_ `{@link Ext.menu.Item#indent indent}: true`.
119573  * This reserves a space for an icon, and indents the Component in line with the other menu items.
119574  * See {@link Ext.form.field.ComboBox}.{@link Ext.form.field.ComboBox#getListParent getListParent} for an example.
119575
119576  * By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}:false`,
119577  * a Menu may be used as a child of a {@link Ext.container.Container Container}.
119578  * {@img Ext.menu.Item/Ext.menu.Item.png Ext.menu.Item component}
119579 __Example Usage__
119580         Ext.create('Ext.menu.Menu', {
119581                 width: 100,
119582                 height: 100,
119583                 margin: '0 0 10 0',
119584                 floating: false,  // usually you want this set to True (default)
119585                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
119586                 items: [{                        
119587                         text: 'regular item 1'        
119588                 },{
119589                     text: 'regular item 2'
119590                 },{
119591                         text: 'regular item 3'  
119592                 }]
119593         }); 
119594         
119595         Ext.create('Ext.menu.Menu', {
119596                 width: 100,
119597                 height: 100,
119598                 plain: true,
119599                 floating: false,  // usually you want this set to True (default)
119600                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
119601                 items: [{                        
119602                         text: 'plain item 1'    
119603                 },{
119604                     text: 'plain item 2'
119605                 },{
119606                         text: 'plain item 3'
119607                 }]
119608         }); 
119609  * @xtype menu
119610  * @markdown
119611  * @constructor
119612  * @param {Object} config The config object
119613  */
119614 Ext.define('Ext.menu.Menu', {
119615     extend: 'Ext.panel.Panel',
119616     alias: 'widget.menu',
119617     requires: [
119618         'Ext.layout.container.Fit',
119619         'Ext.layout.container.VBox',
119620         'Ext.menu.CheckItem',
119621         'Ext.menu.Item',
119622         'Ext.menu.KeyNav',
119623         'Ext.menu.Manager',
119624         'Ext.menu.Separator'
119625     ],
119626
119627     /**
119628      * @cfg {Boolean} allowOtherMenus
119629      * True to allow multiple menus to be displayed at the same time. Defaults to `false`.
119630      * @markdown
119631      */
119632     allowOtherMenus: false,
119633
119634     /**
119635      * @cfg {String} ariaRole @hide
119636      */
119637     ariaRole: 'menu',
119638
119639     /**
119640      * @cfg {Boolean} autoRender @hide
119641      * floating is true, so autoRender always happens
119642      */
119643
119644     /**
119645      * @cfg {String} defaultAlign
119646      * The default {@link Ext.core.Element#getAlignToXY Ext.core.Element#getAlignToXY} anchor position value for this menu
119647      * relative to its element of origin. Defaults to `'tl-bl?'`.
119648      * @markdown
119649      */
119650     defaultAlign: 'tl-bl?',
119651
119652     /**
119653      * @cfg {Boolean} floating
119654      * A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,
119655      * {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be
119656      * used as a child item of another {@link Ext.container.Container Container}.
119657      * @markdown
119658      */
119659     floating: true,
119660
119661     /**
119662      * @cfg {Boolean} @hide
119663      * Menu performs its own size changing constraining, so ensure Component's constraining is not applied
119664      */
119665     constrain: false,
119666
119667     /**
119668      * @cfg {Boolean} hidden
119669      * True to initially render the Menu as hidden, requiring to be shown manually.
119670      * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
119671      * @markdown
119672      */
119673     hidden: true,
119674
119675     /**
119676      * @cfg {Boolean} ignoreParentClicks
119677      * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
119678      * so that the submenu is not dismissed when clicking the parent item. Defaults to `false`.
119679      * @markdown
119680      */
119681     ignoreParentClicks: false,
119682
119683     isMenu: true,
119684
119685     /**
119686      * @cfg {String/Object} layout @hide
119687      */
119688
119689     /**
119690      * @cfg {Boolean} showSeparator True to show the icon separator. (defaults to true).
119691      */
119692     showSeparator : true,
119693
119694     /**
119695      * @cfg {Number} minWidth
119696      * The minimum width of the Menu. Defaults to `120`.
119697      * @markdown
119698      */
119699     minWidth: 120,
119700
119701     /**
119702      * @cfg {Boolean} plain
119703      * True to remove the incised line down the left side of the menu and to not
119704      * indent general Component items. Defaults to `false`.
119705      * @markdown
119706      */
119707
119708     initComponent: function() {
119709         var me = this,
119710             prefix = Ext.baseCSSPrefix,
119711             cls = [prefix + 'menu'],
119712             bodyCls = me.bodyCls ? [me.bodyCls] : [];
119713
119714         me.addEvents(
119715             /**
119716              * @event click
119717              * Fires when this menu is clicked
119718              * @param {Ext.menu.Menu} menu The menu which has been clicked
119719              * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
119720              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.
119721              * @markdown
119722              */
119723             'click',
119724
119725             /**
119726              * @event mouseenter
119727              * Fires when the mouse enters this menu
119728              * @param {Ext.menu.Menu} menu The menu
119729              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
119730              * @markdown
119731              */
119732             'mouseenter',
119733
119734             /**
119735              * @event mouseleave
119736              * Fires when the mouse leaves this menu
119737              * @param {Ext.menu.Menu} menu The menu
119738              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
119739              * @markdown
119740              */
119741             'mouseleave',
119742
119743             /**
119744              * @event mouseover
119745              * Fires when the mouse is hovering over this menu
119746              * @param {Ext.menu.Menu} menu The menu
119747              * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
119748              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
119749              */
119750             'mouseover'
119751         );
119752
119753         Ext.menu.Manager.register(me);
119754
119755         // Menu classes
119756         if (me.plain) {
119757             cls.push(prefix + 'menu-plain');
119758         }
119759         me.cls = cls.join(' ');
119760
119761         // Menu body classes
119762         bodyCls.unshift(prefix + 'menu-body');
119763         me.bodyCls = bodyCls.join(' ');
119764
119765         // Internal vbox layout, with scrolling overflow
119766         // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller
119767         // options if we wish to allow for such configurations on the Menu.
119768         // e.g., scrolling speed, vbox align stretch, etc.
119769         me.layout = {
119770             type: 'vbox',
119771             align: 'stretchmax',
119772             autoSize: true,
119773             clearInnerCtOnLayout: true,
119774             overflowHandler: 'Scroller'
119775         };
119776
119777         // hidden defaults to false if floating is configured as false
119778         if (me.floating === false && me.initialConfig.hidden !== true) {
119779             me.hidden = false;
119780         }
119781
119782         me.callParent(arguments);
119783
119784         me.on('beforeshow', function() {
119785             var hasItems = !!me.items.length;
119786             // FIXME: When a menu has its show cancelled because of no items, it
119787             // gets a visibility: hidden applied to it (instead of the default display: none)
119788             // Not sure why, but we remove this style when we want to show again.
119789             if (hasItems && me.rendered) {
119790                 me.el.setStyle('visibility', null);
119791             }
119792             return hasItems;
119793         });
119794     },
119795
119796     afterRender: function(ct) {
119797         var me = this,
119798             prefix = Ext.baseCSSPrefix,
119799             space = '&#160;';
119800
119801         me.callParent(arguments);
119802
119803         // TODO: Move this to a subTemplate When we support them in the future
119804         if (me.showSeparator) {
119805             me.iconSepEl = me.layout.getRenderTarget().insertFirst({
119806                 cls: prefix + 'menu-icon-separator',
119807                 html: space
119808             });
119809         }
119810
119811         me.focusEl = me.el.createChild({
119812             cls: prefix + 'menu-focus',
119813             tabIndex: '-1',
119814             html: space
119815         });
119816
119817         me.mon(me.el, {
119818             click: me.onClick,
119819             mouseover: me.onMouseOver,
119820             scope: me
119821         });
119822         me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
119823
119824         if (me.showSeparator && ((!Ext.isStrict && Ext.isIE) || Ext.isIE6)) {
119825             me.iconSepEl.setHeight(me.el.getHeight());
119826         }
119827
119828         me.keyNav = Ext.create('Ext.menu.KeyNav', me);
119829     },
119830
119831     afterLayout: function() {
119832         var me = this;
119833         me.callParent(arguments);
119834
119835         // For IE6 & IE quirks, we have to resize the el and body since position: absolute
119836         // floating elements inherit their parent's width, making them the width of
119837         // document.body instead of the width of their contents.
119838         // This includes left/right dock items.
119839         if ((!Ext.iStrict && Ext.isIE) || Ext.isIE6) {
119840             var innerCt = me.layout.getRenderTarget(),
119841                 innerCtWidth = 0,
119842                 dis = me.dockedItems,
119843                 l = dis.length,
119844                 i = 0,
119845                 di, clone, newWidth;
119846
119847             innerCtWidth = innerCt.getWidth();
119848
119849             newWidth = innerCtWidth + me.body.getBorderWidth('lr') + me.body.getPadding('lr');
119850
119851             // First set the body to the new width
119852             me.body.setWidth(newWidth);
119853
119854             // Now we calculate additional width (docked items) and set the el's width
119855             for (; i < l, di = dis.getAt(i); i++) {
119856                 if (di.dock == 'left' || di.dock == 'right') {
119857                     newWidth += di.getWidth();
119858                 }
119859             }
119860             me.el.setWidth(newWidth);
119861         }
119862     },
119863
119864     /**
119865      * Returns whether a menu item can be activated or not.
119866      * @return {Boolean}
119867      */
119868     canActivateItem: function(item) {
119869         return item && !item.isDisabled() && item.isVisible() && (item.canActivate || item.getXTypes().indexOf('menuitem') < 0);
119870     },
119871
119872     /**
119873      * Deactivates the current active item on the menu, if one exists.
119874      */
119875     deactivateActiveItem: function() {
119876         var me = this;
119877
119878         if (me.activeItem) {
119879             me.activeItem.deactivate();
119880             if (!me.activeItem.activated) {
119881                 delete me.activeItem;
119882             }
119883         }
119884         if (me.focusedItem) {
119885             me.focusedItem.blur();
119886             if (!me.focusedItem.$focused) {
119887                 delete me.focusedItem;
119888             }
119889         }
119890     },
119891
119892     // inherit docs
119893     getFocusEl: function() {
119894         return this.focusEl;
119895     },
119896
119897     // inherit docs
119898     hide: function() {
119899         this.deactivateActiveItem();
119900         this.callParent(arguments);
119901     },
119902
119903     // private
119904     getItemFromEvent: function(e) {
119905         return this.getChildByElement(e.getTarget());
119906     },
119907
119908     lookupComponent: function(cmp) {
119909         var me = this;
119910
119911         if (Ext.isString(cmp)) {
119912             cmp = me.lookupItemFromString(cmp);
119913         } else if (Ext.isObject(cmp)) {
119914             cmp = me.lookupItemFromObject(cmp);
119915         }
119916
119917         // Apply our minWidth to all of our child components so it's accounted
119918         // for in our VBox layout
119919         cmp.minWidth = cmp.minWidth || me.minWidth;
119920
119921         return cmp;
119922     },
119923
119924     // private
119925     lookupItemFromObject: function(cmp) {
119926         var me = this,
119927             prefix = Ext.baseCSSPrefix,
119928             cls,
119929             intercept;
119930
119931         if (!cmp.isComponent) {
119932             if (!cmp.xtype) {
119933                 cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
119934             } else {
119935                 cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
119936             }
119937         }
119938
119939         if (cmp.isMenuItem) {
119940             cmp.parentMenu = me;
119941         }
119942
119943         if (!cmp.isMenuItem && !cmp.dock) {
119944             cls = [prefix + 'menu-item', prefix + 'menu-item-cmp'];
119945             intercept = Ext.Function.createInterceptor;
119946
119947             // Wrap focus/blur to control component focus
119948             cmp.focus = intercept(cmp.focus, function() {
119949                 this.$focused = true;
119950             }, cmp);
119951             cmp.blur = intercept(cmp.blur, function() {
119952                 this.$focused = false;
119953             }, cmp);
119954
119955             if (!me.plain && (cmp.indent === true || cmp.iconCls === 'no-icon')) {
119956                 cls.push(prefix + 'menu-item-indent');
119957             }
119958
119959             if (cmp.rendered) {
119960                 cmp.el.addCls(cls);
119961             } else {
119962                 cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
119963             }
119964             cmp.isMenuItem = true;
119965         }
119966         return cmp;
119967     },
119968
119969     // private
119970     lookupItemFromString: function(cmp) {
119971         return (cmp == 'separator' || cmp == '-') ?
119972             Ext.createWidget('menuseparator')
119973             : Ext.createWidget('menuitem', {
119974                 canActivate: false,
119975                 hideOnClick: false,
119976                 plain: true,
119977                 text: cmp
119978             });
119979     },
119980
119981     onClick: function(e) {
119982         var me = this,
119983             item;
119984
119985         if (me.disabled) {
119986             e.stopEvent();
119987             return;
119988         }
119989
119990         if ((e.getTarget() == me.focusEl.dom) || e.within(me.layout.getRenderTarget())) {
119991             item = me.getItemFromEvent(e) || me.activeItem;
119992
119993             if (item) {
119994                 if (item.getXTypes().indexOf('menuitem') >= 0) {
119995                     if (!item.menu || !me.ignoreParentClicks) {
119996                         item.onClick(e);
119997                     } else {
119998                         e.stopEvent();
119999                     }
120000                 }
120001             }
120002             me.fireEvent('click', me, item, e);
120003         }
120004     },
120005
120006     onDestroy: function() {
120007         var me = this;
120008
120009         Ext.menu.Manager.unregister(me);
120010         if (me.rendered) {
120011             me.el.un(me.mouseMonitor);
120012             me.keyNav.destroy();
120013             delete me.keyNav;
120014         }
120015         me.callParent(arguments);
120016     },
120017
120018     onMouseLeave: function(e) {
120019         var me = this;
120020
120021         me.deactivateActiveItem();
120022
120023         if (me.disabled) {
120024             return;
120025         }
120026
120027         me.fireEvent('mouseleave', me, e);
120028     },
120029
120030     onMouseOver: function(e) {
120031         var me = this,
120032             fromEl = e.getRelatedTarget(),
120033             mouseEnter = !me.el.contains(fromEl),
120034             item = me.getItemFromEvent(e);
120035
120036         if (mouseEnter && me.parentMenu) {
120037             me.parentMenu.setActiveItem(me.parentItem);
120038             me.parentMenu.mouseMonitor.mouseenter();
120039         }
120040
120041         if (me.disabled) {
120042             return;
120043         }
120044
120045         if (item) {
120046             me.setActiveItem(item);
120047             if (item.activated && item.expandMenu) {
120048                 item.expandMenu();
120049             }
120050         }
120051         if (mouseEnter) {
120052             me.fireEvent('mouseenter', me, e);
120053         }
120054         me.fireEvent('mouseover', me, item, e);
120055     },
120056
120057     setActiveItem: function(item) {
120058         var me = this;
120059
120060         if (item && (item != me.activeItem && item != me.focusedItem)) {
120061             me.deactivateActiveItem();
120062             if (me.canActivateItem(item)) {
120063                 if (item.activate) {
120064                     item.activate();
120065                     if (item.activated) {
120066                         me.activeItem = item;
120067                         me.focusedItem = item;
120068                         me.focus();
120069                     }
120070                 } else {
120071                     item.focus();
120072                     me.focusedItem = item;
120073                 }
120074             }
120075             item.el.scrollIntoView(me.layout.getRenderTarget());
120076         }
120077     },
120078
120079     /**
120080      * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.core.Element Element}.
120081      * @param {Mixed component} The {@link Ext.Component} or {@link Ext.core.Element} to show the menu by.
120082      * @param {String} position (optional) Alignment position as used by {@link Ext.core.Element#getAlignToXY Ext.core.Element.getAlignToXY}. Defaults to `{@link #defaultAlign}`.
120083      * @param {Array} offsets (optional) Alignment offsets as used by {@link Ext.core.Element#getAlignToXY Ext.core.Element.getAlignToXY}. Defaults to `undefined`.
120084      * @return {Menu} This Menu.
120085      * @markdown
120086      */
120087     showBy: function(cmp, pos, off) {
120088         var me = this,
120089             xy,
120090             region;
120091
120092         if (me.floating && cmp) {
120093             me.layout.autoSize = true;
120094             me.show();
120095
120096             // Component or Element
120097             cmp = cmp.el || cmp;
120098
120099             // Convert absolute to floatParent-relative coordinates if necessary.
120100             xy = me.el.getAlignToXY(cmp, pos || me.defaultAlign, off);
120101             if (me.floatParent) {
120102                 region = me.floatParent.getTargetEl().getViewRegion();
120103                 xy[0] -= region.x;
120104                 xy[1] -= region.y;
120105             }
120106             me.showAt(xy);
120107         }
120108         return me;
120109     },
120110     
120111     // inherit docs
120112     showAt: function(){
120113         this.callParent(arguments);
120114         if (this.floating) {
120115             this.doConstrain();
120116         }    
120117     },
120118
120119     doConstrain : function() {
120120         var me = this,
120121             y = me.el.getY(),
120122             max, full,
120123             vector,
120124             returnY = y, normalY, parentEl, scrollTop, viewHeight;
120125
120126         delete me.height;
120127         me.setSize();
120128         full = me.getHeight();
120129         if (me.floating) {
120130             parentEl = Ext.fly(me.el.dom.parentNode);
120131             scrollTop = parentEl.getScroll().top;
120132             viewHeight = parentEl.getViewSize().height;
120133             //Normalize y by the scroll position for the parent element.  Need to move it into the coordinate space
120134             //of the view.
120135             normalY = y - scrollTop;
120136             max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
120137             if (full > viewHeight) {
120138                 max = viewHeight;
120139                 //Set returnY equal to (0,0) in view space by reducing y by the value of normalY
120140                 returnY = y - normalY;
120141             } else if (max < full) {
120142                 returnY = y - (full - max);
120143                 max = full;
120144             }
120145         }else{
120146             max = me.getHeight();
120147         }
120148         // Always respect maxHeight
120149         if (me.maxHeight){
120150             max = Math.min(me.maxHeight, max);
120151         }
120152         if (full > max && max > 0){
120153             me.layout.autoSize = false;
120154             me.setHeight(max);
120155             if (me.showSeparator){
120156                 me.iconSepEl.setHeight(me.layout.getRenderTarget().dom.scrollHeight);
120157             }
120158         }
120159         vector = me.getConstrainVector();
120160         if (vector) {
120161             me.setPosition(me.getPosition()[0] + vector[0]);
120162         }
120163         me.el.setY(returnY);
120164     }
120165 });
120166 /**
120167  * @class Ext.menu.ColorPicker
120168  * @extends Ext.menu.Menu
120169  * <p>A menu containing a {@link Ext.picker.Color} Component.</p>
120170  * <p>Notes:</p><div class="mdetail-params"><ul>
120171  * <li>Although not listed here, the <b>constructor</b> for this class
120172  * accepts all of the configuration options of <b>{@link Ext.picker.Color}</b>.</li>
120173  * <li>If subclassing ColorMenu, any configuration options for the ColorPicker must be
120174  * applied to the <tt><b>initialConfig</b></tt> property of the ColorMenu.
120175  * Applying {@link Ext.picker.Color ColorPicker} configuration settings to
120176  * <b><tt>this</tt></b> will <b>not</b> affect the ColorPicker's configuration.</li>
120177  * </ul></div>
120178  * {@img Ext.menu.ColorPicker/Ext.menu.ColorPicker.png Ext.menu.ColorPicker component}
120179  * __Example Usage__
120180      var colorPicker = Ext.create('Ext.menu.ColorPicker', {
120181         value: '000000'
120182     });
120183
120184     Ext.create('Ext.menu.Menu', {
120185                 width: 100,
120186                 height: 90,
120187                 floating: false,  // usually you want this set to True (default)
120188                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
120189                 items: [{
120190                     text: 'choose a color',
120191                     menu: colorPicker
120192                 },{
120193             iconCls: 'add16',
120194                     text: 'icon item'
120195                 },{
120196                     text: 'regular item'
120197                 }]
120198         });
120199
120200  * @xtype colormenu
120201  * @author Nicolas Ferrero
120202  */
120203  Ext.define('Ext.menu.ColorPicker', {
120204      extend: 'Ext.menu.Menu',
120205
120206      alias: 'widget.colormenu',
120207
120208      requires: [
120209         'Ext.picker.Color'
120210      ],
120211
120212     /**
120213      * @cfg {Boolean} hideOnClick
120214      * False to continue showing the menu after a date is selected, defaults to true.
120215      */
120216     hideOnClick : true,
120217
120218     /**
120219      * @cfg {String} pickerId
120220      * An id to assign to the underlying color picker. Defaults to <tt>null</tt>.
120221      */
120222     pickerId : null,
120223
120224     /**
120225      * @cfg {Number} maxHeight
120226      * @hide
120227      */
120228
120229     /**
120230      * The {@link Ext.picker.Color} instance for this ColorMenu
120231      * @property picker
120232      * @type ColorPicker
120233      */
120234
120235     /**
120236      * @event click
120237      * @hide
120238      */
120239
120240     /**
120241      * @event itemclick
120242      * @hide
120243      */
120244
120245     initComponent : function(){
120246         var me = this;
120247
120248         Ext.apply(me, {
120249             plain: true,
120250             showSeparator: false,
120251             items: Ext.applyIf({
120252                 cls: Ext.baseCSSPrefix + 'menu-color-item',
120253                 id: me.pickerId,
120254                 xtype: 'colorpicker'
120255             }, me.initialConfig)
120256         });
120257
120258         me.callParent(arguments);
120259
120260         me.picker = me.down('colorpicker');
120261
120262         /**
120263          * @event select
120264          * Fires when a date is selected from the {@link #picker Ext.picker.Color}
120265          * @param {Ext.picker.Color} picker The {@link #picker Ext.picker.Color}
120266          * @param {String} color The 6-digit color hex code (without the # symbol)
120267          */
120268         me.relayEvents(me.picker, ['select']);
120269
120270         if (me.hideOnClick) {
120271             me.on('select', me.hidePickerOnSelect, me);
120272         }
120273     },
120274
120275     /**
120276      * Hides picker on select if hideOnClick is true
120277      * @private
120278      */
120279     hidePickerOnSelect: function() {
120280         Ext.menu.Manager.hideAll();
120281     }
120282  });
120283 /**
120284  * @class Ext.menu.DatePicker
120285  * @extends Ext.menu.Menu
120286  * <p>A menu containing an {@link Ext.picker.Date} Component.</p>
120287  * <p>Notes:</p><div class="mdetail-params"><ul>
120288  * <li>Although not listed here, the <b>constructor</b> for this class
120289  * accepts all of the configuration options of <b>{@link Ext.picker.Date}</b>.</li>
120290  * <li>If subclassing DateMenu, any configuration options for the DatePicker must be
120291  * applied to the <tt><b>initialConfig</b></tt> property of the DateMenu.
120292  * Applying {@link Ext.picker.Date DatePicker} configuration settings to
120293  * <b><tt>this</tt></b> will <b>not</b> affect the DatePicker's configuration.</li>
120294  * </ul></div>
120295  * {@img Ext.menu.DatePicker/Ext.menu.DatePicker.png Ext.menu.DatePicker component}
120296  * __Example Usage__
120297      var dateMenu = Ext.create('Ext.menu.DatePicker', {
120298         handler: function(dp, date){
120299             Ext.Msg.alert('Date Selected', 'You choose {0}.', Ext.Date.format(date, 'M j, Y'));
120300
120301         }
120302     });
120303
120304     Ext.create('Ext.menu.Menu', {
120305                 width: 100,
120306                 height: 90,
120307                 floating: false,  // usually you want this set to True (default)
120308                 renderTo: Ext.getBody(),  // usually rendered by it's containing component
120309                 items: [{
120310                     text: 'choose a date',
120311                     menu: dateMenu
120312                 },{
120313             iconCls: 'add16',
120314                     text: 'icon item'
120315                 },{
120316                     text: 'regular item'
120317                 }]
120318         });
120319
120320  * @xtype datemenu
120321  * @author Nicolas Ferrero
120322  */
120323  Ext.define('Ext.menu.DatePicker', {
120324      extend: 'Ext.menu.Menu',
120325
120326      alias: 'widget.datemenu',
120327
120328      requires: [
120329         'Ext.picker.Date'
120330      ],
120331
120332     /**
120333      * @cfg {Boolean} hideOnClick
120334      * False to continue showing the menu after a date is selected, defaults to true.
120335      */
120336     hideOnClick : true,
120337
120338     /**
120339      * @cfg {String} pickerId
120340      * An id to assign to the underlying date picker. Defaults to <tt>null</tt>.
120341      */
120342     pickerId : null,
120343
120344     /**
120345      * @cfg {Number} maxHeight
120346      * @hide
120347      */
120348
120349     /**
120350      * The {@link Ext.picker.Date} instance for this DateMenu
120351      * @property picker
120352      * @type Ext.picker.Date
120353      */
120354
120355     /**
120356      * @event click
120357      * @hide
120358      */
120359
120360     /**
120361      * @event itemclick
120362      * @hide
120363      */
120364
120365     initComponent : function(){
120366         var me = this;
120367
120368         Ext.apply(me, {
120369             showSeparator: false,
120370             plain: true,
120371             items: Ext.applyIf({
120372                 cls: Ext.baseCSSPrefix + 'menu-date-item',
120373                 id: me.pickerId,
120374                 xtype: 'datepicker'
120375             }, me.initialConfig)
120376         });
120377
120378         me.callParent(arguments);
120379
120380         me.picker = me.down('datepicker');
120381         /**
120382          * @event select
120383          * Fires when a date is selected from the {@link #picker Ext.picker.Date}
120384          * @param {Ext.picker.Date} picker The {@link #picker Ext.picker.Date}
120385          * @param {Date} date The selected date
120386          */
120387         me.relayEvents(me.picker, ['select']);
120388
120389         if (me.hideOnClick) {
120390             me.on('select', me.hidePickerOnSelect, me);
120391         }
120392     },
120393
120394     hidePickerOnSelect: function() {
120395         Ext.menu.Manager.hideAll();
120396     }
120397  });
120398 /**
120399  * @class Ext.panel.Tool
120400  * @extends Ext.Component
120401
120402 This class is used to display small visual icons in the header of a panel. There are a set of
120403 25 icons that can be specified by using the {@link #type} config. The {@link #handler} config
120404 can be used to provide a function that will respond to any click events. In general, this class
120405 will not be instantiated directly, rather it will be created by specifying the {@link Ext.panel.Panel#tools}
120406 configuration on the Panel itself.
120407
120408 __Example Usage__
120409
120410     Ext.create('Ext.panel.Panel', {
120411        width: 200,
120412        height: 200,
120413        renderTo: document.body,
120414        title: 'A Panel',
120415        tools: [{
120416            type: 'help',
120417            handler: function(){
120418                // show help here
120419            }
120420        }, {
120421            itemId: 'refresh',
120422            type: 'refresh',
120423            hidden: true,
120424            handler: function(){
120425                // do refresh
120426            }
120427        }, {
120428            type: 'search',
120429            handler: function(event, target, owner, tool){
120430                // do search
120431                owner.child('#refresh').show();
120432            }
120433        }]
120434     });
120435
120436  * @markdown
120437  * @xtype tool
120438  */
120439 Ext.define('Ext.panel.Tool', {
120440     extend: 'Ext.Component',
120441     requires: ['Ext.tip.QuickTipManager'],
120442     alias: 'widget.tool',
120443
120444     baseCls: Ext.baseCSSPrefix + 'tool',
120445     disabledCls: Ext.baseCSSPrefix + 'tool-disabled',
120446     toolPressedCls: Ext.baseCSSPrefix + 'tool-pressed',
120447     toolOverCls: Ext.baseCSSPrefix + 'tool-over',
120448     ariaRole: 'button',
120449     renderTpl: ['<img src="{blank}" class="{baseCls}-{type}" role="presentation"/>'],
120450     
120451     /**
120452      * @cfg {Function} handler
120453      * A function to execute when the tool is clicked.
120454      * Arguments passed are:
120455      * <ul>
120456      * <li><b>event</b> : Ext.EventObject<div class="sub-desc">The click event.</div></li>
120457      * <li><b>toolEl</b> : Ext.core.Element<div class="sub-desc">The tool Element.</div></li>
120458      * <li><b>panel</b> : Ext.panel.Panel<div class="sub-desc">The host Panel</div></li>
120459      * <li><b>tool</b> : Ext.panel.Tool<div class="sub-desc">The tool object</div></li>
120460      * </ul>
120461      */
120462     
120463     /**
120464      * @cfg {Object} scope
120465      * The scope to execute the {@link #handler} function. Defaults to the tool.
120466      */
120467     
120468     /**
120469      * @cfg {String} type
120470      * The type of tool to render. The following types are available:
120471      * <ul>
120472      * <li>close</li>
120473      * <li>collapse</li>
120474      * <li>down</li>
120475      * <li>expand</li>
120476      * <li>gear</li>
120477      * <li>help</li>
120478      * <li>left</li>
120479      * <li>maximize</li>
120480      * <li>minimize</li>
120481      * <li>minus</li>
120482      * <li>move</li>
120483      * <li>next</li>
120484      * <li>pin</li>
120485      * <li>plus</li>
120486      * <li>prev</li>
120487      * <li>print</li>
120488      * <li>refresh</li>
120489      * <li>resize</li>
120490      * <li>restore</li>
120491      * <li>right</li>
120492      * <li>save</li>
120493      * <li>search</li>
120494      * <li>toggle</li>
120495      * <li>unpin</li>
120496      * <li>up</li>
120497      * </ul>
120498      */
120499     
120500     /**
120501      * @cfg {String/Object} tooltip 
120502      * The tooltip for the tool - can be a string to be used as innerHTML (html tags are accepted) or QuickTips config object
120503      */
120504     
120505     /**
120506      * @cfg {Boolean} stopEvent
120507      * Defaults to true. Specify as false to allow click event to propagate.
120508      */
120509     stopEvent: true,
120510
120511     initComponent: function() {
120512         var me = this;
120513         me.addEvents(
120514             /**
120515              * @event click
120516              * Fires when the tool is clicked
120517              * @param {Ext.panel.Tool} this
120518              * @param {Ext.EventObject} e The event object
120519              */
120520             'click'
120521         );
120522         
120523         var types = [
120524             'close', 
120525             'collapse', 
120526             'down', 
120527             'expand', 
120528             'gear', 
120529             'help', 
120530             'left', 
120531             'maximize', 
120532             'minimize', 
120533             'minus', 
120534             'move', 
120535             'next', 
120536             'pin', 
120537             'plus', 
120538             'prev', 
120539             'print', 
120540             'refresh', 
120541             'resize', 
120542             'restore', 
120543             'right', 
120544             'save', 
120545             'search', 
120546             'toggle',
120547             'unpin', 
120548             'up'
120549         ];
120550         
120551         if (me.id && Ext.Array.indexOf(types, me.id) > -1 && Ext.global.console) {
120552             Ext.global.console.warn('When specifying a tool you should use the type option, the id can conflict now that tool is a Component');
120553         }
120554         
120555         me.type = me.type || me.id;
120556
120557         Ext.applyIf(me.renderData, {
120558             baseCls: me.baseCls,
120559             blank: Ext.BLANK_IMAGE_URL,
120560             type: me.type
120561         });
120562         me.renderSelectors.toolEl = '.' + me.baseCls + '-' + me.type;
120563         me.callParent();
120564     },
120565
120566     // inherit docs
120567     afterRender: function() {
120568         var me = this;
120569         me.callParent(arguments);
120570         if (me.qtip) {
120571             if (Ext.isObject(me.qtip)) {
120572                 Ext.tip.QuickTipManager.register(Ext.apply({
120573                     target: me.id
120574                 }, me.qtip));
120575             }
120576             else {
120577                 me.toolEl.dom.qtip = me.qtip;
120578             }
120579         }
120580
120581         me.mon(me.toolEl, {
120582             click: me.onClick,
120583             mousedown: me.onMouseDown,
120584             mouseover: me.onMouseOver,
120585             mouseout: me.onMouseOut,
120586             scope: me
120587         });
120588     },
120589
120590     /**
120591      * Set the type of the tool. Allows the icon to be changed.
120592      * @param {String} type The new type. See the {@link #type} config.
120593      * @return {Ext.panel.Tool} this
120594      */
120595     setType: function(type) {
120596         var me = this;
120597         
120598         me.type = type;
120599         if (me.rendered) {
120600             me.toolEl.dom.className = me.baseCls + '-' + type;
120601         }
120602         return me;
120603     },
120604
120605     /**
120606      * Binds this tool to a component.
120607      * @private
120608      * @param {Ext.Component} component The component
120609      */
120610     bindTo: function(component) {
120611         this.owner = component;
120612     },
120613
120614     /**
120615      * Fired when the tool element is clicked
120616      * @private
120617      * @param {Ext.EventObject} e
120618      * @param {HTMLElement} target The target element
120619      */
120620     onClick: function(e, target) {
120621         var me = this,
120622             owner;
120623             
120624         if (me.disabled) {
120625             return false;
120626         }
120627         owner = me.owner || me.ownerCt;
120628
120629         //remove the pressed + over class
120630         me.el.removeCls(me.toolPressedCls);
120631         me.el.removeCls(me.toolOverCls);
120632
120633         if (me.stopEvent !== false) {
120634             e.stopEvent();
120635         }
120636
120637         Ext.callback(me.handler, me.scope || me, [e, target, owner, me]);
120638         me.fireEvent('click', me, e);
120639         return true;
120640     },
120641     
120642     // inherit docs
120643     onDestroy: function(){
120644         if (Ext.isObject(this.tooltip)) {
120645             Ext.tip.QuickTipManager.unregister(this.id);
120646         }    
120647         this.callParent();
120648     },
120649
120650     /**
120651      * Called then the user pressing their mouse button down on a tool
120652      * Adds the press class ({@link #toolPressedCls})
120653      * @private
120654      */
120655     onMouseDown: function() {
120656         if (this.disabled) {
120657             return false;
120658         }
120659
120660         this.el.addCls(this.toolPressedCls);
120661     },
120662
120663     /**
120664      * Called when the user rolls over a tool
120665      * Adds the over class ({@link #toolOverCls})
120666      * @private
120667      */
120668     onMouseOver: function() {
120669         if (this.disabled) {
120670             return false;
120671         }
120672         this.el.addCls(this.toolOverCls);
120673     },
120674
120675     /**
120676      * Called when the user rolls out from a tool.
120677      * Removes the over class ({@link #toolOverCls})
120678      * @private
120679      */
120680     onMouseOut: function() {
120681         this.el.removeCls(this.toolOverCls);
120682     }
120683 });
120684 /**
120685  * @class Ext.resizer.Handle
120686  * @extends Ext.Component
120687  *
120688  * Provides a handle for 9-point resizing of Elements or Components.
120689  */
120690 Ext.define('Ext.resizer.Handle', {
120691     extend: 'Ext.Component',
120692     handleCls: '',
120693     baseHandleCls: Ext.baseCSSPrefix + 'resizable-handle',
120694     // Ext.resizer.Resizer.prototype.possiblePositions define the regions
120695     // which will be passed in as a region configuration.
120696     region: '',
120697
120698     onRender: function() {
120699         this.addCls(
120700             this.baseHandleCls,
120701             this.baseHandleCls + '-' + this.region,
120702             this.handleCls
120703         );
120704         this.callParent(arguments);
120705         this.el.unselectable();
120706     }
120707 });
120708
120709 /**
120710  * @class Ext.resizer.Resizer
120711  * <p>Applies drag handles to an element or component to make it resizable. The
120712  * drag handles are inserted into the element (or component's element) and
120713  * positioned absolute.</p>
120714  *
120715  * <p>Textarea and img elements will be wrapped with an additional div because
120716  * these elements do not support child nodes. The original element can be accessed
120717  * through the originalTarget property.</p>
120718  *
120719  * <p>Here is the list of valid resize handles:</p>
120720  * <pre>
120721 Value   Description
120722 ------  -------------------
120723  'n'     north
120724  's'     south
120725  'e'     east
120726  'w'     west
120727  'nw'    northwest
120728  'sw'    southwest
120729  'se'    southeast
120730  'ne'    northeast
120731  'all'   all
120732 </pre>
120733  * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
120734  * <p>Here's an example showing the creation of a typical Resizer:</p>
120735  * <pre><code>
120736     <div id="elToResize" style="width:200px; height:100px; background-color:#000000;"></div>
120737
120738     Ext.create('Ext.resizer.Resizer', {
120739         el: 'elToResize',
120740         handles: 'all',
120741         minWidth: 200,
120742         minHeight: 100,
120743         maxWidth: 500,
120744         maxHeight: 400,
120745         pinned: true
120746     });
120747 </code></pre>
120748 */
120749 Ext.define('Ext.resizer.Resizer', {
120750     mixins: {
120751         observable: 'Ext.util.Observable'
120752     },
120753     uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],
120754
120755     alternateClassName: 'Ext.Resizable',
120756
120757     handleCls: Ext.baseCSSPrefix + 'resizable-handle',
120758     pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
120759     overCls:   Ext.baseCSSPrefix + 'resizable-over',
120760     proxyCls:  Ext.baseCSSPrefix + 'resizable-proxy',
120761     wrapCls:   Ext.baseCSSPrefix + 'resizable-wrap',
120762
120763     /**
120764      * @cfg {Boolean} dynamic
120765      * <p>Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during dragging.
120766      * This is <code>true</code> by default, but the {@link Ext.Component Component} class passes <code>false</code> when it
120767      * is configured as {@link Ext.Component#resizable}.</p>
120768      * <p>If specified as <code>false</code>, a proxy element is displayed during the resize operation, and the {@link #target}
120769      * is updated on mouseup.</p>
120770      */
120771     dynamic: true,
120772
120773     /**
120774      * @cfg {String} handles String consisting of the resize handles to display. Defaults to 's e se' for
120775      * Elements and fixed position Components. Defaults to 8 point resizing for floating Components (such as Windows).
120776      * Specify either <code>'all'</code> or any of <code>'n s e w ne nw se sw'</code>.
120777      */
120778     handles: 's e se',
120779
120780     /**
120781      * @cfg {Number} height Optional. The height to set target to in pixels (defaults to null)
120782      */
120783     height : null,
120784
120785     /**
120786      * @cfg {Number} width Optional. The width to set the target to in pixels (defaults to null)
120787      */
120788     width : null,
120789
120790     /**
120791      * @cfg {Number} minHeight The minimum height for the element (defaults to 20)
120792      */
120793     minHeight : 20,
120794
120795     /**
120796      * @cfg {Number} minWidth The minimum width for the element (defaults to 20)
120797      */
120798     minWidth : 20,
120799
120800     /**
120801      * @cfg {Number} maxHeight The maximum height for the element (defaults to 10000)
120802      */
120803     maxHeight : 10000,
120804
120805     /**
120806      * @cfg {Number} maxWidth The maximum width for the element (defaults to 10000)
120807      */
120808     maxWidth : 10000,
120809
120810     /**
120811      * @cfg {Boolean} pinned True to ensure that the resize handles are always
120812      * visible, false indicates resizing by cursor changes only (defaults to false)
120813      */
120814     pinned: false,
120815
120816     /**
120817      * @cfg {Boolean} preserveRatio True to preserve the original ratio between height
120818      * and width during resize (defaults to false)
120819      */
120820     preserveRatio: false,
120821
120822     /**
120823      * @cfg {Boolean} transparent True for transparent handles. This is only applied at config time. (defaults to false)
120824      */
120825     transparent: false,
120826
120827     /**
120828      * @cfg {Mixed} constrainTo Optional. An element, or a {@link Ext.util.Region} into which the resize operation
120829      * must be constrained.
120830      */
120831
120832     possiblePositions: {
120833         n:  'north',
120834         s:  'south',
120835         e:  'east',
120836         w:  'west',
120837         se: 'southeast',
120838         sw: 'southwest',
120839         nw: 'northwest',
120840         ne: 'northeast'
120841     },
120842
120843     /**
120844      * @cfg {Mixed} target The Element or Component to resize.
120845      */
120846
120847     /**
120848      * Outer element for resizing behavior.
120849      * @type Ext.core.Element
120850      * @property el
120851      */
120852
120853     constructor: function(config) {
120854         var me = this,
120855             target,
120856             tag,
120857             handles = me.handles,
120858             handleCls,
120859             possibles,
120860             len,
120861             i = 0,
120862             pos;
120863
120864         this.addEvents(
120865             /**
120866              * @event beforeresize
120867              * Fired before resize is allowed. Return false to cancel resize.
120868              * @param {Ext.resizer.Resizer} this
120869              * @param {Number} width The start width
120870              * @param {Number} height The start height
120871              * @param {Ext.EventObject} e The mousedown event
120872              */
120873             'beforeresize',
120874             /**
120875              * @event resizedrag
120876              * Fires during resizing. Return false to cancel resize.
120877              * @param {Ext.resizer.Resizer} this
120878              * @param {Number} width The new width
120879              * @param {Number} height The new height
120880              * @param {Ext.EventObject} e The mousedown event
120881              */
120882             'resizedrag',
120883             /**
120884              * @event resize
120885              * Fired after a resize.
120886              * @param {Ext.resizer.Resizer} this
120887              * @param {Number} width The new width
120888              * @param {Number} height The new height
120889              * @param {Ext.EventObject} e The mouseup event
120890              */
120891             'resize'
120892         );
120893
120894         if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
120895             target = config;
120896             config = arguments[1] || {};
120897             config.target = target;
120898         }
120899         // will apply config to this
120900         me.mixins.observable.constructor.call(me, config);
120901
120902         // If target is a Component, ensure that we pull the element out.
120903         // Resizer must examine the underlying Element.
120904         target = me.target;
120905         if (target) {
120906             if (target.isComponent) {
120907                 me.el = target.getEl();
120908                 if (target.minWidth) {
120909                     me.minWidth = target.minWidth;
120910                 }
120911                 if (target.minHeight) {
120912                     me.minHeight = target.minHeight;
120913                 }
120914                 if (target.maxWidth) {
120915                     me.maxWidth = target.maxWidth;
120916                 }
120917                 if (target.maxHeight) {
120918                     me.maxHeight = target.maxHeight;
120919                 }
120920                 if (target.floating) {
120921                     if (!this.hasOwnProperty('handles')) {
120922                         this.handles = 'n ne e se s sw w nw';
120923                     }
120924                 }
120925             } else {
120926                 me.el = me.target = Ext.get(target);
120927             }
120928         }
120929         // Backwards compatibility with Ext3.x's Resizable which used el as a config.
120930         else {
120931             me.target = me.el = Ext.get(me.el);
120932         }
120933
120934         // Tags like textarea and img cannot
120935         // have children and therefore must
120936         // be wrapped
120937         tag = me.el.dom.tagName;
120938         if (tag == 'TEXTAREA' || tag == 'IMG') {
120939             /**
120940              * Reference to the original resize target if the element of the original
120941              * resize target was an IMG or a TEXTAREA which must be wrapped in a DIV.
120942              * @type Mixed
120943              * @property originalTarget
120944              */
120945             me.originalTarget = me.target;
120946             me.target = me.el = me.el.wrap({
120947                 cls: me.wrapCls,
120948                 id: me.el.id + '-rzwrap'
120949             });
120950
120951             // Transfer originalTarget's positioning/sizing
120952             me.el.setPositioning(me.originalTarget.getPositioning());
120953             me.originalTarget.clearPositioning();
120954             var box = me.originalTarget.getBox();
120955             me.el.setBox(box);
120956         }
120957
120958         // Position the element, this enables us to absolute position
120959         // the handles within this.el
120960         me.el.position();
120961         if (me.pinned) {
120962             me.el.addCls(me.pinnedCls);
120963         }
120964
120965         /**
120966          * @type Ext.resizer.ResizeTracker
120967          * @property resizeTracker
120968          */
120969         me.resizeTracker = Ext.create('Ext.resizer.ResizeTracker', {
120970             disabled: me.disabled,
120971             target: me.target,
120972             constrainTo: me.constrainTo,
120973             overCls: me.overCls,
120974             throttle: me.throttle,
120975             originalTarget: me.originalTarget,
120976             delegate: '.' + me.handleCls,
120977             dynamic: me.dynamic,
120978             preserveRatio: me.preserveRatio,
120979             minHeight: me.minHeight,
120980             maxHeight: me.maxHeight,
120981             minWidth: me.minWidth,
120982             maxWidth: me.maxWidth
120983         });
120984
120985         // Relay the ResizeTracker's superclass events as our own resize events
120986         me.resizeTracker.on('mousedown', me.onBeforeResize, me);
120987         me.resizeTracker.on('drag', me.onResize, me);
120988         me.resizeTracker.on('dragend', me.onResizeEnd, me);
120989
120990         if (me.handles == 'all') {
120991             me.handles = 'n s e w ne nw se sw';
120992         }
120993
120994         handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
120995         possibles = me.possiblePositions;
120996         len = handles.length;
120997         handleCls = me.handleCls + ' ' + (this.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';
120998
120999         for(; i < len; i++){
121000             // if specified and possible, create
121001             if (handles[i] && possibles[handles[i]]) {
121002                 pos = possibles[handles[i]];
121003                 // store a reference in this.east, this.west, etc
121004
121005                 me[pos] = Ext.create('Ext.Component', {
121006                     owner: this,
121007                     region: pos,
121008                     cls: handleCls + pos,
121009                     renderTo: me.el
121010                 });
121011                 me[pos].el.unselectable();
121012                 if (me.transparent) {
121013                     me[pos].el.setOpacity(0);
121014                 }
121015             }
121016         }
121017
121018         // Constrain within configured maxima
121019         if (Ext.isNumber(me.width)) {
121020             me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
121021         }
121022         if (Ext.isNumber(me.height)) {
121023             me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
121024         }
121025
121026         // Size the element
121027         if (me.width != null || me.height != null) {
121028             if (me.originalTarget) {
121029                 me.originalTarget.setWidth(me.width);
121030                 me.originalTarget.setHeight(me.height);
121031             }
121032             me.resizeTo(me.width, me.height);
121033         }
121034
121035         me.forceHandlesHeight();
121036     },
121037
121038     disable: function() {
121039         this.resizeTracker.disable();
121040     },
121041
121042     enable: function() {
121043         this.resizeTracker.enable();
121044     },
121045
121046     /**
121047      * @private Relay the Tracker's mousedown event as beforeresize
121048      * @param tracker The Resizer
121049      * @param e The Event
121050      */
121051     onBeforeResize: function(tracker, e) {
121052         var b = this.target.getBox();
121053         return this.fireEvent('beforeresize', this, b.width, b.height, e);
121054     },
121055
121056     /**
121057      * @private Relay the Tracker's drag event as resizedrag
121058      * @param tracker The Resizer
121059      * @param e The Event
121060      */
121061     onResize: function(tracker, e) {
121062         var me = this,
121063             b = me.target.getBox();
121064         me.forceHandlesHeight();
121065         return me.fireEvent('resizedrag', me, b.width, b.height, e);
121066     },
121067
121068     /**
121069      * @private Relay the Tracker's dragend event as resize
121070      * @param tracker The Resizer
121071      * @param e The Event
121072      */
121073     onResizeEnd: function(tracker, e) {
121074         var me = this,
121075             b = me.target.getBox();
121076         me.forceHandlesHeight();
121077         return me.fireEvent('resize', me, b.width, b.height, e);
121078     },
121079
121080     /**
121081      * Perform a manual resize and fires the 'resize' event.
121082      * @param {Number} width
121083      * @param {Number} height
121084      */
121085     resizeTo : function(width, height){
121086         this.target.setSize(width, height);
121087         this.fireEvent('resize', this, width, height, null);
121088     },
121089
121090     /**
121091      * <p>Returns the element that was configured with the el or target config property.
121092      * If a component was configured with the target property then this will return the
121093      * element of this component.<p>
121094      * <p>Textarea and img elements will be wrapped with an additional div because
121095       * these elements do not support child nodes. The original element can be accessed
121096      * through the originalTarget property.</p>
121097      * @return {Element} element
121098      */
121099     getEl : function() {
121100         return this.el;
121101     },
121102
121103     /**
121104      * <p>Returns the element or component that was configured with the target config property.<p>
121105      * <p>Textarea and img elements will be wrapped with an additional div because
121106       * these elements do not support child nodes. The original element can be accessed
121107      * through the originalTarget property.</p>
121108      * @return {Element/Component}
121109      */
121110     getTarget: function() {
121111         return this.target;
121112     },
121113
121114     destroy: function() {
121115         var h;
121116         for (var i = 0, l = this.handles.length; i < l; i++) {
121117             h = this[this.possiblePositions[this.handles[i]]];
121118             delete h.owner;
121119             Ext.destroy(h);
121120         }
121121     },
121122
121123     /**
121124      * @private
121125      * Fix IE6 handle height issue.
121126      */
121127     forceHandlesHeight : function() {
121128         var me = this,
121129             handle;
121130         if (Ext.isIE6) {
121131             handle = me.east; 
121132             if (handle) {
121133                 handle.setHeight(me.el.getHeight());
121134             }
121135             handle = me.west; 
121136             if (handle) {
121137                 handle.setHeight(me.el.getHeight());
121138             }
121139             me.el.repaint();
121140         }
121141     }
121142 });
121143
121144 /**
121145  * @class Ext.resizer.ResizeTracker
121146  * @extends Ext.dd.DragTracker
121147  */
121148 Ext.define('Ext.resizer.ResizeTracker', {
121149     extend: 'Ext.dd.DragTracker',
121150     dynamic: true,
121151     preserveRatio: false,
121152
121153     // Default to no constraint
121154     constrainTo: null,
121155
121156     constructor: function(config) {
121157         var me = this;
121158
121159         if (!config.el) {
121160             if (config.target.isComponent) {
121161                 me.el = config.target.getEl();
121162             } else {
121163                 me.el = config.target;
121164             }
121165         }
121166         this.callParent(arguments);
121167
121168         // Ensure that if we are preserving aspect ratio, the largest minimum is honoured
121169         if (me.preserveRatio && me.minWidth && me.minHeight) {
121170             var widthRatio = me.minWidth / me.el.getWidth(),
121171                 heightRatio = me.minHeight / me.el.getHeight();
121172
121173             // largest ratio of minimum:size must be preserved.
121174             // So if a 400x200 pixel image has
121175             // minWidth: 50, maxWidth: 50, the maxWidth will be 400 * (50/200)... that is 100
121176             if (heightRatio > widthRatio) {
121177                 me.minWidth = me.el.getWidth() * heightRatio;
121178             } else {
121179                 me.minHeight = me.el.getHeight() * widthRatio;
121180             }
121181         }
121182
121183         // If configured as throttled, create an instance version of resize which calls
121184         // a throttled function to perform the resize operation.
121185         if (me.throttle) {
121186             var throttledResizeFn = Ext.Function.createThrottled(function() {
121187                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
121188                 }, me.throttle);
121189
121190             me.resize = function(box, direction, atEnd) {
121191                 if (atEnd) {
121192                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
121193                 } else {
121194                     throttledResizeFn.apply(null, arguments);
121195                 }
121196             };
121197         }
121198     },
121199
121200     onBeforeStart: function(e) {
121201         // record the startBox
121202         this.startBox = this.el.getBox();
121203     },
121204
121205     /**
121206      * @private
121207      * Returns the object that will be resized on every mousemove event.
121208      * If dynamic is false, this will be a proxy, otherwise it will be our actual target.
121209      */
121210     getDynamicTarget: function() {
121211         var d = this.target;
121212         if (this.dynamic) {
121213             return d;
121214         } else if (!this.proxy) {
121215             this.proxy = d.isComponent ? d.getProxy().addCls(Ext.baseCSSPrefix + 'resizable-proxy') : d.createProxy({tag: 'div', cls: Ext.baseCSSPrefix + 'resizable-proxy', id: d.id + '-rzproxy'}, Ext.getBody());
121216             this.proxy.removeCls(Ext.baseCSSPrefix + 'proxy-el');
121217         }
121218         this.proxy.show();
121219         return this.proxy;
121220     },
121221
121222     onStart: function(e) {
121223         // returns the Ext.ResizeHandle that the user started dragging
121224         this.activeResizeHandle = Ext.getCmp(this.getDragTarget().id);
121225
121226         // If we are using a proxy, ensure it is sized.
121227         if (!this.dynamic) {
121228             this.resize(this.startBox, {
121229                 horizontal: 'none',
121230                 vertical: 'none'
121231             });
121232         }
121233     },
121234
121235     onDrag: function(e) {
121236         // dynamic resizing, update dimensions during resize
121237         if (this.dynamic || this.proxy) {
121238             this.updateDimensions(e);
121239         }
121240     },
121241
121242     updateDimensions: function(e, atEnd) {
121243         var me = this,
121244             region = me.activeResizeHandle.region,
121245             offset = me.getOffset(me.constrainTo ? 'dragTarget' : null),
121246             box = me.startBox,
121247             ratio,
121248             widthAdjust = 0,
121249             heightAdjust = 0,
121250             adjustX = 0,
121251             adjustY = 0,
121252             dragRatio,
121253             horizDir = offset[0] < 0 ? 'right' : 'left',
121254             vertDir = offset[1] < 0 ? 'down' : 'up',
121255             oppositeCorner,
121256             axis; // 1 = x, 2 = y, 3 = x and y.
121257
121258         switch (region) {
121259             case 'south':
121260                 heightAdjust = offset[1];
121261                 axis = 2;
121262                 break;
121263             case 'north':
121264                 heightAdjust = -offset[1];
121265                 adjustY = -heightAdjust;
121266                 axis = 2;
121267                 break;
121268             case 'east':
121269                 widthAdjust = offset[0];
121270                 axis = 1;
121271                 break;
121272             case 'west':
121273                 widthAdjust = -offset[0];
121274                 adjustX = -widthAdjust;
121275                 axis = 1;
121276                 break;
121277             case 'northeast':
121278                 heightAdjust = -offset[1];
121279                 adjustY = -heightAdjust;
121280                 widthAdjust = offset[0];
121281                 oppositeCorner = [box.x, box.y + box.height];
121282                 axis = 3;
121283                 break;
121284             case 'southeast':
121285                 heightAdjust = offset[1];
121286                 widthAdjust = offset[0];
121287                 oppositeCorner = [box.x, box.y];
121288                 axis = 3;
121289                 break;
121290             case 'southwest':
121291                 widthAdjust = -offset[0];
121292                 adjustX = -widthAdjust;
121293                 heightAdjust = offset[1];
121294                 oppositeCorner = [box.x + box.width, box.y];
121295                 axis = 3;
121296                 break;
121297             case 'northwest':
121298                 heightAdjust = -offset[1];
121299                 adjustY = -heightAdjust;
121300                 widthAdjust = -offset[0];
121301                 adjustX = -widthAdjust;
121302                 oppositeCorner = [box.x + box.width, box.y + box.height];
121303                 axis = 3;
121304                 break;
121305         }
121306
121307         var newBox = {
121308             width: box.width + widthAdjust,
121309             height: box.height + heightAdjust,
121310             x: box.x + adjustX,
121311             y: box.y + adjustY
121312         };
121313
121314         // out of bounds
121315         if (newBox.width < me.minWidth || newBox.width > me.maxWidth) {
121316             newBox.width = Ext.Number.constrain(newBox.width, me.minWidth, me.maxWidth);
121317             newBox.x = me.lastX || newBox.x;
121318         } else {
121319             me.lastX = newBox.x;
121320         }
121321         if (newBox.height < me.minHeight || newBox.height > me.maxHeight) {
121322             newBox.height = Ext.Number.constrain(newBox.height, me.minHeight, me.maxHeight);
121323             newBox.y = me.lastY || newBox.y;
121324         } else {
121325             me.lastY = newBox.y;
121326         }
121327
121328         // If this is configured to preserve the aspect ratio, or they are dragging using the shift key
121329         if (me.preserveRatio || e.shiftKey) {
121330             var newHeight,
121331                 newWidth;
121332
121333             ratio = me.startBox.width / me.startBox.height;
121334
121335             // Calculate aspect ratio constrained values.
121336             newHeight = Math.min(Math.max(me.minHeight, newBox.width / ratio), me.maxHeight);
121337             newWidth = Math.min(Math.max(me.minWidth, newBox.height * ratio), me.maxWidth);
121338
121339             // X axis: width-only change, height must obey
121340             if (axis == 1) {
121341                 newBox.height = newHeight;
121342             }
121343
121344             // Y axis: height-only change, width must obey
121345             else if (axis == 2) {
121346                 newBox.width = newWidth;
121347             }
121348
121349             // Corner drag.
121350             else {
121351                 // Drag ratio is the ratio of the mouse point from the opposite corner.
121352                 // Basically what edge we are dragging, a horizontal edge or a vertical edge.
121353                 dragRatio = Math.abs(oppositeCorner[0] - this.lastXY[0]) / Math.abs(oppositeCorner[1] - this.lastXY[1]);
121354
121355                 // If drag ratio > aspect ratio then width is dominant and height must obey
121356                 if (dragRatio > ratio) {
121357                     newBox.height = newHeight;
121358                 } else {
121359                     newBox.width = newWidth;
121360                 }
121361
121362                 // Handle dragging start coordinates
121363                 if (region == 'northeast') {
121364                     newBox.y = box.y - (newBox.height - box.height);
121365                 } else if (region == 'northwest') {
121366                     newBox.y = box.y - (newBox.height - box.height);
121367                     newBox.x = box.x - (newBox.width - box.width);
121368                 } else if (region == 'southwest') {
121369                     newBox.x = box.x - (newBox.width - box.width);
121370                 }
121371             }
121372         }
121373
121374         if (heightAdjust === 0) {
121375             vertDir = 'none';
121376         }
121377         if (widthAdjust === 0) {
121378             horizDir = 'none';
121379         }
121380         me.resize(newBox, {
121381             horizontal: horizDir,
121382             vertical: vertDir
121383         }, atEnd);
121384     },
121385
121386     getResizeTarget: function(atEnd) {
121387         return atEnd ? this.target : this.getDynamicTarget();
121388     },
121389
121390     resize: function(box, direction, atEnd) {
121391         var target = this.getResizeTarget(atEnd);
121392         if (target.isComponent) {
121393             if (target.floating) {
121394                 target.setPagePosition(box.x, box.y);
121395             }
121396             target.setSize(box.width, box.height);
121397         } else {
121398             target.setBox(box);
121399             // update the originalTarget if this was wrapped.
121400             if (this.originalTarget) {
121401                 this.originalTarget.setBox(box);
121402             }
121403         }
121404     },
121405
121406     onEnd: function(e) {
121407         this.updateDimensions(e, true);
121408         if (this.proxy) {
121409             this.proxy.hide();
121410         }
121411     }
121412 });
121413
121414 /**
121415  * @class Ext.resizer.SplitterTracker
121416  * @extends Ext.dd.DragTracker
121417  * Private utility class for Ext.Splitter.
121418  * @private
121419  */
121420 Ext.define('Ext.resizer.SplitterTracker', {
121421     extend: 'Ext.dd.DragTracker',
121422     requires: ['Ext.util.Region'],
121423     enabled: true,
121424     
121425     overlayCls: Ext.baseCSSPrefix + 'resizable-overlay',
121426
121427     getPrevCmp: function() {
121428         var splitter = this.getSplitter();
121429         return splitter.previousSibling();
121430     },
121431
121432     getNextCmp: function() {
121433         var splitter = this.getSplitter();
121434         return splitter.nextSibling();
121435     },
121436
121437     // ensure the tracker is enabled, store boxes of previous and next
121438     // components and calculate the constrain region
121439     onBeforeStart: function(e) {
121440         var me = this,
121441             prevCmp = me.getPrevCmp(),
121442             nextCmp = me.getNextCmp();
121443
121444         // SplitterTracker is disabled if any of its adjacents are collapsed.
121445         if (nextCmp.collapsed || prevCmp.collapsed) {
121446             return false;
121447         }
121448         // store boxes of previous and next
121449         me.prevBox  = prevCmp.getEl().getBox();
121450         me.nextBox  = nextCmp.getEl().getBox();
121451         me.constrainTo = me.calculateConstrainRegion();
121452     },
121453
121454     // We move the splitter el. Add the proxy class.
121455     onStart: function(e) {
121456         var splitter = this.getSplitter(),
121457             overlay;
121458             
121459         splitter.addCls(splitter.baseCls + '-active');
121460         overlay = this.overlay =  Ext.getBody().createChild({
121461             cls: this.overlayCls, 
121462             html: '&#160;'
121463         });
121464         overlay.unselectable();
121465         overlay.setSize(Ext.core.Element.getViewWidth(true), Ext.core.Element.getViewHeight(true));
121466         overlay.show();
121467     },
121468
121469     // calculate the constrain Region in which the splitter el may be moved.
121470     calculateConstrainRegion: function() {
121471         var me         = this,
121472             splitter   = me.getSplitter(),
121473             splitWidth = splitter.getWidth(),
121474             defaultMin = splitter.defaultSplitMin,
121475             orient     = splitter.orientation,
121476             prevBox    = me.prevBox,
121477             prevCmp    = me.getPrevCmp(),
121478             nextBox    = me.nextBox,
121479             nextCmp    = me.getNextCmp(),
121480             // prev and nextConstrainRegions are the maximumBoxes minus the
121481             // minimumBoxes. The result is always the intersection
121482             // of these two boxes.
121483             prevConstrainRegion, nextConstrainRegion;
121484
121485         // vertical splitters, so resizing left to right
121486         if (orient === 'vertical') {
121487
121488             // Region constructor accepts (top, right, bottom, left)
121489             // anchored/calculated from the left
121490             prevConstrainRegion = Ext.create('Ext.util.Region',
121491                 prevBox.y,
121492                 // Right boundary is x + maxWidth if there IS a maxWidth.
121493                 // Otherwise it is calculated based upon the minWidth of the next Component
121494                 (prevCmp.maxWidth ? prevBox.x + prevCmp.maxWidth : nextBox.right - (nextCmp.minWidth || defaultMin)) + splitWidth,
121495                 prevBox.bottom,
121496                 prevBox.x + (prevCmp.minWidth || defaultMin)
121497             );
121498             // anchored/calculated from the right
121499             nextConstrainRegion = Ext.create('Ext.util.Region',
121500                 nextBox.y,
121501                 nextBox.right - (nextCmp.minWidth || defaultMin),
121502                 nextBox.bottom,
121503                 // Left boundary is right - maxWidth if there IS a maxWidth.
121504                 // Otherwise it is calculated based upon the minWidth of the previous Component
121505                 (nextCmp.maxWidth ? nextBox.right - nextCmp.maxWidth : prevBox.x + (prevBox.minWidth || defaultMin)) - splitWidth
121506             );
121507         } else {
121508             // anchored/calculated from the top
121509             prevConstrainRegion = Ext.create('Ext.util.Region',
121510                 prevBox.y + (prevCmp.minHeight || defaultMin),
121511                 prevBox.right,
121512                 // Bottom boundary is y + maxHeight if there IS a maxHeight.
121513                 // Otherwise it is calculated based upon the minWidth of the next Component
121514                 (prevCmp.maxHeight ? prevBox.y + prevCmp.maxHeight : nextBox.bottom - (nextCmp.minHeight || defaultMin)) + splitWidth,
121515                 prevBox.x
121516             );
121517             // anchored/calculated from the bottom
121518             nextConstrainRegion = Ext.create('Ext.util.Region',
121519                 // Top boundary is bottom - maxHeight if there IS a maxHeight.
121520                 // Otherwise it is calculated based upon the minHeight of the previous Component
121521                 (nextCmp.maxHeight ? nextBox.bottom - nextCmp.maxHeight : prevBox.y + (prevCmp.minHeight || defaultMin)) - splitWidth,
121522                 nextBox.right,
121523                 nextBox.bottom - (nextCmp.minHeight || defaultMin),
121524                 nextBox.x
121525             );
121526         }
121527
121528         // intersection of the two regions to provide region draggable
121529         return prevConstrainRegion.intersect(nextConstrainRegion);
121530     },
121531
121532     // Performs the actual resizing of the previous and next components
121533     performResize: function(e) {
121534         var me       = this,
121535             offset   = me.getOffset('dragTarget'),
121536             splitter = me.getSplitter(),
121537             orient   = splitter.orientation,
121538             prevCmp  = me.getPrevCmp(),
121539             nextCmp  = me.getNextCmp(),
121540             owner    = splitter.ownerCt,
121541             layout   = owner.getLayout();
121542
121543         // Inhibit automatic container layout caused by setSize calls below.
121544         owner.suspendLayout = true;
121545
121546         if (orient === 'vertical') {
121547             if (prevCmp) {
121548                 if (!prevCmp.maintainFlex) {
121549                     delete prevCmp.flex;
121550                     prevCmp.setSize(me.prevBox.width + offset[0], prevCmp.getHeight());
121551                 }
121552             }
121553             if (nextCmp) {
121554                 if (!nextCmp.maintainFlex) {
121555                     delete nextCmp.flex;
121556                     nextCmp.setSize(me.nextBox.width - offset[0], nextCmp.getHeight());
121557                 }
121558             }
121559         // verticals
121560         } else {
121561             if (prevCmp) {
121562                 if (!prevCmp.maintainFlex) {
121563                     delete prevCmp.flex;
121564                     prevCmp.setSize(prevCmp.getWidth(), me.prevBox.height + offset[1]);
121565                 }
121566             }
121567             if (nextCmp) {
121568                 if (!nextCmp.maintainFlex) {
121569                     delete nextCmp.flex;
121570                     nextCmp.setSize(prevCmp.getWidth(), me.nextBox.height - offset[1]);
121571                 }
121572             }
121573         }
121574         delete owner.suspendLayout;
121575         layout.onLayout();
121576     },
121577
121578     // perform the resize and remove the proxy class from the splitter el
121579     onEnd: function(e) {
121580         var me = this,
121581             splitter = me.getSplitter();
121582             
121583         splitter.removeCls(splitter.baseCls + '-active');
121584          if (me.overlay) {
121585              me.overlay.remove();
121586              delete me.overlay;
121587         }
121588         me.performResize();
121589     },
121590
121591     // Track the proxy and set the proper XY coordinates
121592     // while constraining the drag
121593     onDrag: function(e) {
121594         var me        = this,
121595             offset    = me.getOffset('dragTarget'),
121596             splitter  = me.getSplitter(),
121597             splitEl   = splitter.getEl(),
121598             orient    = splitter.orientation;
121599
121600         if (orient === "vertical") {
121601             splitEl.setX(me.startRegion.left + offset[0]);
121602         } else {
121603             splitEl.setY(me.startRegion.top + offset[1]);
121604         }
121605     },
121606
121607     getSplitter: function() {
121608         return Ext.getCmp(this.getDragCt().id);
121609     }
121610 });
121611 /**
121612  * @class Ext.selection.CellModel
121613  * @extends Ext.selection.Model
121614  * @private
121615  */
121616 Ext.define('Ext.selection.CellModel', {
121617     extend: 'Ext.selection.Model',
121618     alias: 'selection.cellmodel',
121619     requires: ['Ext.util.KeyNav'],
121620     
121621     /**
121622      * @cfg {Boolean} enableKeyNav
121623      * Turns on/off keyboard navigation within the grid. Defaults to true.
121624      */
121625     enableKeyNav: true,
121626     
121627     /**
121628      * @cfg {Boolean} preventWrap
121629      * Set this configuration to true to prevent wrapping around of selection as
121630      * a user navigates to the first or last column. Defaults to false.
121631      */
121632     preventWrap: false,
121633
121634     constructor: function(){
121635         this.addEvents(
121636             /**
121637              * @event deselect
121638              * Fired after a cell is deselected
121639              * @param {Ext.selection.CellModel} this
121640              * @param {Ext.data.Model} record The record of the deselected cell
121641              * @param {Number} row The row index deselected
121642              * @param {Number} column The column index deselected
121643              */
121644             'deselect',
121645             
121646             /**
121647              * @event select
121648              * Fired after a cell is selected
121649              * @param {Ext.selection.CellModel} this
121650              * @param {Ext.data.Model} record The record of the selected cell
121651              * @param {Number} row The row index selected
121652              * @param {Number} column The column index selected
121653              */
121654             'select'
121655         );
121656         this.callParent(arguments);    
121657     },
121658
121659     bindComponent: function(view) {
121660         var me = this;
121661         me.primaryView = view;
121662         me.views = me.views || [];
121663         me.views.push(view);
121664         me.bind(view.getStore(), true);
121665
121666         view.on({
121667             cellmousedown: me.onMouseDown,
121668             refresh: me.onViewRefresh,
121669             scope: me
121670         });
121671
121672         if (me.enableKeyNav) {
121673             me.initKeyNav(view);
121674         }
121675     },
121676
121677     initKeyNav: function(view) {
121678         var me = this;
121679         
121680         if (!view.rendered) {
121681             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
121682             return;
121683         }
121684
121685         view.el.set({
121686             tabIndex: -1
121687         });
121688
121689         // view.el has tabIndex -1 to allow for
121690         // keyboard events to be passed to it.
121691         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
121692             up: me.onKeyUp,
121693             down: me.onKeyDown,
121694             right: me.onKeyRight,
121695             left: me.onKeyLeft,
121696             tab: me.onKeyTab,
121697             scope: me
121698         });
121699     },
121700     
121701     getHeaderCt: function() {
121702         return this.primaryView.headerCt;
121703     },
121704
121705     onKeyUp: function(e, t) {
121706         this.move('up', e);
121707     },
121708
121709     onKeyDown: function(e, t) {
121710         this.move('down', e);
121711     },
121712
121713     onKeyLeft: function(e, t) {
121714         this.move('left', e);
121715     },
121716     
121717     onKeyRight: function(e, t) {
121718         this.move('right', e);
121719     },
121720     
121721     move: function(dir, e) {
121722         var me = this,
121723             pos = me.primaryView.walkCells(me.getCurrentPosition(), dir, e, me.preventWrap);
121724         if (pos) {
121725             me.setCurrentPosition(pos);
121726         }
121727         return pos;
121728     },
121729
121730     /**
121731      * Returns the current position in the format {row: row, column: column}
121732      */
121733     getCurrentPosition: function() {
121734         return this.position;
121735     },
121736     
121737     /**
121738      * Sets the current position
121739      * @param {Object} position The position to set.
121740      */
121741     setCurrentPosition: function(pos) {
121742         var me = this;
121743         
121744         if (me.position) {
121745             me.onCellDeselect(me.position);
121746         }
121747         if (pos) {
121748             me.onCellSelect(pos);
121749         }
121750         me.position = pos;
121751     },
121752
121753     /**
121754      * Set the current position based on where the user clicks.
121755      * @private
121756      */
121757     onMouseDown: function(view, cell, cellIndex, record, row, rowIndex, e) {
121758         this.setCurrentPosition({
121759             row: rowIndex,
121760             column: cellIndex
121761         });
121762     },
121763
121764     // notify the view that the cell has been selected to update the ui
121765     // appropriately and bring the cell into focus
121766     onCellSelect: function(position) {
121767         var me = this,
121768             store = me.view.getStore(),
121769             record = store.getAt(position.row);
121770
121771         me.doSelect(record);
121772         me.primaryView.onCellSelect(position);
121773         // TODO: Remove temporary cellFocus call here.
121774         me.primaryView.onCellFocus(position);
121775         me.fireEvent('select', me, record, position.row, position.column);
121776     },
121777
121778     // notify view that the cell has been deselected to update the ui
121779     // appropriately
121780     onCellDeselect: function(position) {
121781         var me = this,
121782             store = me.view.getStore(),
121783             record = store.getAt(position.row);
121784
121785         me.doDeselect(record);
121786         me.primaryView.onCellDeselect(position);
121787         me.fireEvent('deselect', me, record, position.row, position.column);
121788     },
121789
121790     onKeyTab: function(e, t) {
121791         var me = this,
121792             direction = e.shiftKey ? 'left' : 'right',
121793             editingPlugin = me.view.editingPlugin,
121794             position = me.move(direction, e);
121795
121796         if (editingPlugin && position && me.wasEditing) {
121797             editingPlugin.startEditByPosition(position);
121798         }
121799         delete me.wasEditing;
121800     },
121801
121802     onEditorTab: function(editingPlugin, e) {
121803         var me = this,
121804             direction = e.shiftKey ? 'left' : 'right',
121805             position  = me.move(direction, e);
121806
121807         if (position) {
121808             editingPlugin.startEditByPosition(position);
121809             me.wasEditing = true;
121810         }
121811     },
121812
121813     refresh: function() {
121814         var pos = this.getCurrentPosition();
121815         if (pos) {
121816             this.onCellSelect(pos);
121817         }
121818     },
121819
121820     onViewRefresh: function() {
121821         var pos = this.getCurrentPosition();
121822         if (pos) {
121823             this.onCellDeselect(pos);
121824             this.setCurrentPosition(null);
121825         }
121826     },
121827
121828     selectByPosition: function(position) {
121829         this.setCurrentPosition(position);
121830     }
121831 });
121832 /**
121833  * @class Ext.selection.RowModel
121834  * @extends Ext.selection.Model
121835  * 
121836  * Implement row based navigation via keyboard.
121837  *
121838  * Must synchronize across grid sections
121839  */
121840 Ext.define('Ext.selection.RowModel', {
121841     extend: 'Ext.selection.Model',
121842     alias: 'selection.rowmodel',
121843     requires: ['Ext.util.KeyNav'],
121844     
121845     /**
121846      * @private
121847      * Number of pixels to scroll to the left/right when pressing
121848      * left/right keys.
121849      */
121850     deltaScroll: 5,
121851     
121852     /**
121853      * @cfg {Boolean} enableKeyNav
121854      * 
121855      * Turns on/off keyboard navigation within the grid. Defaults to true.
121856      */
121857     enableKeyNav: true,
121858     
121859     constructor: function(){
121860         this.addEvents(
121861             /**
121862              * @event deselect
121863              * Fired after a record is deselected
121864              * @param {Ext.selection.RowSelectionModel} this
121865              * @param {Ext.data.Model} record The deselected record
121866              * @param {Number} index The row index deselected
121867              */
121868             'deselect',
121869             
121870             /**
121871              * @event select
121872              * Fired after a record is selected
121873              * @param {Ext.selection.RowSelectionModel} this
121874              * @param {Ext.data.Model} record The selected record
121875              * @param {Number} index The row index selected
121876              */
121877             'select'
121878         );
121879         this.callParent(arguments);    
121880     },
121881
121882     bindComponent: function(view) {
121883         var me = this;
121884         
121885         me.views = me.views || [];
121886         me.views.push(view);
121887         me.bind(view.getStore(), true);
121888
121889         view.on({
121890             itemmousedown: me.onRowMouseDown,
121891             scope: me
121892         });
121893
121894         if (me.enableKeyNav) {
121895             me.initKeyNav(view);
121896         }
121897     },
121898
121899     initKeyNav: function(view) {
121900         var me = this;
121901         
121902         if (!view.rendered) {
121903             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
121904             return;
121905         }
121906
121907         view.el.set({
121908             tabIndex: -1
121909         });
121910
121911         // view.el has tabIndex -1 to allow for
121912         // keyboard events to be passed to it.
121913         me.keyNav = new Ext.util.KeyNav(view.el, {
121914             up: me.onKeyUp,
121915             down: me.onKeyDown,
121916             right: me.onKeyRight,
121917             left: me.onKeyLeft,
121918             pageDown: me.onKeyPageDown,
121919             pageUp: me.onKeyPageUp,
121920             home: me.onKeyHome,
121921             end: me.onKeyEnd,
121922             scope: me
121923         });
121924         view.el.on(Ext.EventManager.getKeyEvent(), me.onKeyPress, me);
121925     },
121926
121927     // Returns the number of rows currently visible on the screen or
121928     // false if there were no rows. This assumes that all rows are
121929     // of the same height and the first view is accurate.
121930     getRowsVisible: function() {
121931         var rowsVisible = false,
121932             view = this.views[0],
121933             row = view.getNode(0),
121934             rowHeight, gridViewHeight;
121935
121936         if (row) {
121937             rowHeight = Ext.fly(row).getHeight();
121938             gridViewHeight = view.el.getHeight();
121939             rowsVisible = Math.floor(gridViewHeight / rowHeight);
121940         }
121941
121942         return rowsVisible;
121943     },
121944
121945     // go to last visible record in grid.
121946     onKeyEnd: function(e, t) {
121947         var me = this,
121948             last = me.store.getAt(me.store.getCount() - 1);
121949             
121950         if (last) {
121951             if (e.shiftKey) {
121952                 me.selectRange(last, me.lastFocused || 0);
121953                 me.setLastFocused(last);
121954             } else if (e.ctrlKey) {
121955                 me.setLastFocused(last);
121956             } else {
121957                 me.doSelect(last);
121958             }
121959         }
121960     },
121961
121962     // go to first visible record in grid.
121963     onKeyHome: function(e, t) {
121964         var me = this,
121965             first = me.store.getAt(0);
121966             
121967         if (first) {
121968             if (e.shiftKey) {
121969                 me.selectRange(first, me.lastFocused || 0);
121970                 me.setLastFocused(first);
121971             } else if (e.ctrlKey) {
121972                 me.setLastFocused(first);
121973             } else {
121974                 me.doSelect(first, false);
121975             }
121976         }
121977     },
121978
121979     // Go one page up from the lastFocused record in the grid.
121980     onKeyPageUp: function(e, t) {
121981         var me = this,
121982             rowsVisible = me.getRowsVisible(),
121983             selIdx,
121984             prevIdx,
121985             prevRecord,
121986             currRec;
121987             
121988         if (rowsVisible) {
121989             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
121990             prevIdx = selIdx - rowsVisible;
121991             if (prevIdx < 0) {
121992                 prevIdx = 0;
121993             }
121994             prevRecord = me.store.getAt(prevIdx);
121995             if (e.shiftKey) {
121996                 currRec = me.store.getAt(selIdx);
121997                 me.selectRange(prevRecord, currRec, e.ctrlKey, 'up');
121998                 me.setLastFocused(prevRecord);
121999             } else if (e.ctrlKey) {
122000                 e.preventDefault();
122001                 me.setLastFocused(prevRecord);
122002             } else {
122003                 me.doSelect(prevRecord);
122004             }
122005
122006         }
122007     },
122008
122009     // Go one page down from the lastFocused record in the grid.
122010     onKeyPageDown: function(e, t) {
122011         var me = this,
122012             rowsVisible = me.getRowsVisible(),
122013             selIdx,
122014             nextIdx,
122015             nextRecord,
122016             currRec;
122017             
122018         if (rowsVisible) {
122019             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
122020             nextIdx = selIdx + rowsVisible;
122021             if (nextIdx >= me.store.getCount()) {
122022                 nextIdx = me.store.getCount() - 1;
122023             }
122024             nextRecord = me.store.getAt(nextIdx);
122025             if (e.shiftKey) {
122026                 currRec = me.store.getAt(selIdx);
122027                 me.selectRange(nextRecord, currRec, e.ctrlKey, 'down');
122028                 me.setLastFocused(nextRecord);
122029             } else if (e.ctrlKey) {
122030                 // some browsers, this means go thru browser tabs
122031                 // attempt to stop.
122032                 e.preventDefault();
122033                 me.setLastFocused(nextRecord);
122034             } else {
122035                 me.doSelect(nextRecord);
122036             }
122037         }
122038     },
122039
122040     // Select/Deselect based on pressing Spacebar.
122041     // Assumes a SIMPLE selectionmode style
122042     onKeyPress: function(e, t) {
122043         if (e.getKey() === e.SPACE) {
122044             e.stopEvent();
122045             var me = this,
122046                 record = me.lastFocused;
122047                 
122048             if (record) {
122049                 if (me.isSelected(record)) {
122050                     me.doDeselect(record, false);
122051                 } else {
122052                     me.doSelect(record, true);
122053                 }
122054             }
122055         }
122056     },
122057
122058     // Navigate one record up. This could be a selection or
122059     // could be simply focusing a record for discontiguous
122060     // selection. Provides bounds checking.
122061     onKeyUp: function(e, t) {
122062         var me = this,
122063             view = me.views[0],
122064             idx  = me.store.indexOf(me.lastFocused),
122065             record;
122066             
122067         if (idx > 0) {
122068             // needs to be the filtered count as thats what
122069             // will be visible.
122070             record = me.store.getAt(idx - 1);
122071             if (e.shiftKey && me.lastFocused) {
122072                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
122073                     me.doDeselect(me.lastFocused, true);
122074                     me.setLastFocused(record);
122075                 } else if (!me.isSelected(me.lastFocused)) {
122076                     me.doSelect(me.lastFocused, true);
122077                     me.doSelect(record, true);
122078                 } else {
122079                     me.doSelect(record, true);
122080                 }
122081             } else if (e.ctrlKey) {
122082                 me.setLastFocused(record);
122083             } else {
122084                 me.doSelect(record);
122085                 //view.focusRow(idx - 1);
122086             }
122087         }
122088         // There was no lastFocused record, and the user has pressed up
122089         // Ignore??
122090         //else if (this.selected.getCount() == 0) {
122091         //    
122092         //    this.doSelect(record);
122093         //    //view.focusRow(idx - 1);
122094         //}
122095     },
122096
122097     // Navigate one record down. This could be a selection or
122098     // could be simply focusing a record for discontiguous
122099     // selection. Provides bounds checking.
122100     onKeyDown: function(e, t) {
122101         var me = this,
122102             view = me.views[0],
122103             idx  = me.store.indexOf(me.lastFocused),
122104             record;
122105             
122106         // needs to be the filtered count as thats what
122107         // will be visible.
122108         if (idx + 1 < me.store.getCount()) {
122109             record = me.store.getAt(idx + 1);
122110             if (me.selected.getCount() === 0) {
122111                 me.doSelect(record);
122112                 //view.focusRow(idx + 1);
122113             } else if (e.shiftKey && me.lastFocused) {
122114                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
122115                     me.doDeselect(me.lastFocused, true);
122116                     me.setLastFocused(record);
122117                 } else if (!me.isSelected(me.lastFocused)) {
122118                     me.doSelect(me.lastFocused, true);
122119                     me.doSelect(record, true);
122120                 } else {
122121                     me.doSelect(record, true);
122122                 }
122123             } else if (e.ctrlKey) {
122124                 me.setLastFocused(record);
122125             } else {
122126                 me.doSelect(record);
122127                 //view.focusRow(idx + 1);
122128             }
122129         }
122130     },
122131     
122132     scrollByDeltaX: function(delta) {
122133         var view    = this.views[0],
122134             section = view.up(),
122135             hScroll = section.horizontalScroller;
122136             
122137         if (hScroll) {
122138             hScroll.scrollByDeltaX(delta);
122139         }
122140     },
122141     
122142     onKeyLeft: function(e, t) {
122143         this.scrollByDeltaX(-this.deltaScroll);
122144     },
122145     
122146     onKeyRight: function(e, t) {
122147         this.scrollByDeltaX(this.deltaScroll);
122148     },
122149
122150     // Select the record with the event included so that
122151     // we can take into account ctrlKey, shiftKey, etc
122152     onRowMouseDown: function(view, record, item, index, e) {
122153         view.el.focus();
122154         this.selectWithEvent(record, e);
122155     },
122156
122157     // Allow the GridView to update the UI by
122158     // adding/removing a CSS class from the row.
122159     onSelectChange: function(record, isSelected, suppressEvent) {
122160         var me      = this,
122161             views   = me.views,
122162             viewsLn = views.length,
122163             store   = me.store,
122164             rowIdx  = store.indexOf(record),
122165             i = 0;
122166             
122167         for (; i < viewsLn; i++) {
122168             if (isSelected) {
122169                 views[i].onRowSelect(rowIdx, suppressEvent);
122170                 if (!suppressEvent) {
122171                     me.fireEvent('select', me, record, rowIdx);
122172                 }
122173             } else {
122174                 views[i].onRowDeselect(rowIdx, suppressEvent);
122175                 if (!suppressEvent) {
122176                     me.fireEvent('deselect', me, record, rowIdx);
122177                 }
122178             }
122179         }
122180     },
122181
122182     // Provide indication of what row was last focused via
122183     // the gridview.
122184     onLastFocusChanged: function(oldFocused, newFocused, supressFocus) {
122185         var views   = this.views,
122186             viewsLn = views.length,
122187             store   = this.store,
122188             rowIdx,
122189             i = 0;
122190             
122191         if (oldFocused) {
122192             rowIdx = store.indexOf(oldFocused);
122193             if (rowIdx != -1) {
122194                 for (; i < viewsLn; i++) {
122195                     views[i].onRowFocus(rowIdx, false);
122196                 }
122197             }
122198         }
122199
122200         if (newFocused) {
122201             rowIdx = store.indexOf(newFocused);
122202             if (rowIdx != -1) {
122203                 for (i = 0; i < viewsLn; i++) {
122204                     views[i].onRowFocus(rowIdx, true, supressFocus);
122205                 }
122206             }
122207         }
122208     },
122209     
122210     onEditorTab: function(editingPlugin, e) {
122211         var me = this,
122212             view = me.views[0],
122213             record = editingPlugin.getActiveRecord(),
122214             header = editingPlugin.getActiveColumn(),
122215             position = view.getPosition(record, header),
122216             direction = e.shiftKey ? 'left' : 'right',
122217             newPosition  = view.walkCells(position, direction, e, this.preventWrap);
122218             
122219         if (newPosition) {
122220             editingPlugin.startEditByPosition(newPosition);
122221         }
122222     },
122223     
122224     selectByPosition: function(position) {
122225         var record = this.store.getAt(position.row);
122226         this.select(record);
122227     }
122228 });
122229 /**
122230  * @class Ext.selection.CheckboxModel
122231  * @extends Ext.selection.RowModel
122232  *
122233  * A selection model that renders a column of checkboxes that can be toggled to
122234  * select or deselect rows. The default mode for this selection model is MULTI.
122235  *
122236  * The selection model will inject a header for the checkboxes in the first view
122237  * and according to the 'injectCheckbox' configuration.
122238  */
122239 Ext.define('Ext.selection.CheckboxModel', {
122240     alias: 'selection.checkboxmodel',
122241     extend: 'Ext.selection.RowModel',
122242
122243     /**
122244      * @cfg {String} mode
122245      * Modes of selection.
122246      * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'MULTI'
122247      */
122248     mode: 'MULTI',
122249
122250     /**
122251      * @cfg {Mixed} injectCheckbox
122252      * Instructs the SelectionModel whether or not to inject the checkbox header
122253      * automatically or not. (Note: By not placing the checkbox in manually, the
122254      * grid view will need to be rendered 2x on initial render.)
122255      * Supported values are a Number index, false and the strings 'first' and 'last'.
122256      * Default is 0.
122257      */
122258     injectCheckbox: 0,
122259
122260     /**
122261      * @cfg {Boolean} checkOnly <tt>true</tt> if rows can only be selected by clicking on the
122262      * checkbox column (defaults to <tt>false</tt>).
122263      */
122264     checkOnly: false,
122265
122266     // private
122267     checkerOnCls: Ext.baseCSSPrefix + 'grid-hd-checker-on',
122268
122269     bindComponent: function() {
122270         this.sortable = false;
122271         this.callParent(arguments);
122272
122273         var view     = this.views[0],
122274             headerCt = view.headerCt;
122275
122276         if (this.injectCheckbox !== false) {
122277             if (this.injectCheckbox == 'first') {
122278                 this.injectCheckbox = 0;
122279             } else if (this.injectCheckbox == 'last') {
122280                 this.injectCheckbox = headerCt.getColumnCount();
122281             }
122282             headerCt.add(this.injectCheckbox,  this.getHeaderConfig());
122283         }
122284         headerCt.on('headerclick', this.onHeaderClick, this);
122285     },
122286
122287     /**
122288      * Toggle the ui header between checked and unchecked state.
122289      * @param {Boolean} isChecked
122290      * @private
122291      */
122292     toggleUiHeader: function(isChecked) {
122293         var view     = this.views[0],
122294             headerCt = view.headerCt,
122295             checkHd  = headerCt.child('gridcolumn[isCheckerHd]');
122296
122297         if (checkHd) {
122298             if (isChecked) {
122299                 checkHd.el.addCls(this.checkerOnCls);
122300             } else {
122301                 checkHd.el.removeCls(this.checkerOnCls);
122302             }
122303         }
122304     },
122305
122306     /**
122307      * Toggle between selecting all and deselecting all when clicking on
122308      * a checkbox header.
122309      */
122310     onHeaderClick: function(headerCt, header, e) {
122311         if (header.isCheckerHd) {
122312             e.stopEvent();
122313             var isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
122314             if (isChecked) {
122315                 // We have to supress the event or it will scrollTo the change
122316                 this.deselectAll(true);
122317             } else {
122318                 // We have to supress the event or it will scrollTo the change
122319                 this.selectAll(true);
122320             }
122321         }
122322     },
122323
122324     /**
122325      * Retrieve a configuration to be used in a HeaderContainer.
122326      * This should be used when injectCheckbox is set to false.
122327      */
122328     getHeaderConfig: function() {
122329         return {
122330             isCheckerHd: true,
122331             text : '&#160;',
122332             width: 24,
122333             sortable: false,
122334             fixed: true,
122335             hideable: false,
122336             menuDisabled: true,
122337             dataIndex: '',
122338             cls: Ext.baseCSSPrefix + 'column-header-checkbox ',
122339             renderer: Ext.Function.bind(this.renderer, this)
122340         };
122341     },
122342
122343     /**
122344      * Generates the HTML to be rendered in the injected checkbox column for each row.
122345      * Creates the standard checkbox markup by default; can be overridden to provide custom rendering.
122346      * See {@link Ext.grid.column.Column#renderer} for description of allowed parameters.
122347      */
122348     renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
122349         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
122350         return '<div class="' + Ext.baseCSSPrefix + 'grid-row-checker">&#160;</div>';
122351     },
122352
122353     // override
122354     onRowMouseDown: function(view, record, item, index, e) {
122355         view.el.focus();
122356         var me = this,
122357             checker = e.getTarget('.' + Ext.baseCSSPrefix + 'grid-row-checker');
122358
122359         // checkOnly set, but we didn't click on a checker.
122360         if (me.checkOnly && !checker) {
122361             return;
122362         }
122363
122364         if (checker) {
122365             var mode = me.getSelectionMode();
122366             // dont change the mode if its single otherwise
122367             // we would get multiple selection
122368             if (mode !== 'SINGLE') {
122369                 me.setSelectionMode('SIMPLE');
122370             }
122371             me.selectWithEvent(record, e);
122372             me.setSelectionMode(mode);
122373         } else {
122374             me.selectWithEvent(record, e);
122375         }
122376     },
122377
122378     /**
122379      * Synchronize header checker value as selection changes.
122380      * @private
122381      */
122382     onSelectChange: function(record, isSelected) {
122383         this.callParent([record, isSelected]);
122384         // check to see if all records are selected
122385         var hdSelectStatus = this.selected.getCount() === this.store.getCount();
122386         this.toggleUiHeader(hdSelectStatus);
122387     }
122388 });
122389
122390 /**
122391  * @class Ext.selection.TreeModel
122392  * @extends Ext.selection.RowModel
122393  *
122394  * Adds custom behavior for left/right keyboard navigation for use with a tree.
122395  * Depends on the view having an expand and collapse method which accepts a
122396  * record.
122397  * 
122398  * @private
122399  */
122400 Ext.define('Ext.selection.TreeModel', {
122401     extend: 'Ext.selection.RowModel',
122402     alias: 'selection.treemodel',
122403     
122404     // typically selection models prune records from the selection
122405     // model when they are removed, because the TreeView constantly
122406     // adds/removes records as they are expanded/collapsed
122407     pruneRemoved: false,
122408     
122409     onKeyRight: function(e, t) {
122410         var focused = this.getLastFocused(),
122411             view    = this.view;
122412             
122413         if (focused) {
122414             // tree node is already expanded, go down instead
122415             // this handles both the case where we navigate to firstChild and if
122416             // there are no children to the nextSibling
122417             if (focused.isExpanded()) {
122418                 this.onKeyDown(e, t);
122419             // if its not a leaf node, expand it
122420             } else if (!focused.isLeaf()) {
122421                 view.expand(focused);
122422             }
122423         }
122424     },
122425     
122426     onKeyLeft: function(e, t) {
122427         var focused = this.getLastFocused(),
122428             view    = this.view,
122429             viewSm  = view.getSelectionModel(),
122430             parentNode, parentRecord;
122431
122432         if (focused) {
122433             parentNode = focused.parentNode;
122434             // if focused node is already expanded, collapse it
122435             if (focused.isExpanded()) {
122436                 view.collapse(focused);
122437             // has a parentNode and its not root
122438             // TODO: this needs to cover the case where the root isVisible
122439             } else if (parentNode && !parentNode.isRoot()) {
122440                 // Select a range of records when doing multiple selection.
122441                 if (e.shiftKey) {
122442                     viewSm.selectRange(parentNode, focused, e.ctrlKey, 'up');
122443                     viewSm.setLastFocused(parentNode);
122444                 // just move focus, not selection
122445                 } else if (e.ctrlKey) {
122446                     viewSm.setLastFocused(parentNode);
122447                 // select it
122448                 } else {
122449                     viewSm.select(parentNode);
122450                 }
122451             }
122452         }
122453     },
122454     
122455     onKeyPress: function(e, t) {
122456         var selected, checked;
122457         
122458         if (e.getKey() === e.SPACE || e.getKey() === e.ENTER) {
122459             e.stopEvent();
122460             selected = this.getLastSelected();
122461             if (selected && selected.isLeaf()) {
122462                 checked = selected.get('checked');
122463                 if (Ext.isBoolean(checked)) {
122464                     selected.set('checked', !checked);
122465                 }
122466             }
122467         } else {
122468             this.callParent(arguments);
122469         }
122470     }
122471 });
122472
122473 /**
122474  * @private
122475  * @class Ext.slider.Thumb
122476  * @extends Ext.Base
122477  * @private
122478  * Represents a single thumb element on a Slider. This would not usually be created manually and would instead
122479  * be created internally by an {@link Ext.slider.Multi Ext.Slider}.
122480  */
122481 Ext.define('Ext.slider.Thumb', {
122482     requires: ['Ext.dd.DragTracker', 'Ext.util.Format'],
122483     /**
122484      * @private
122485      * @property topThumbZIndex
122486      * @type Number
122487      * The number used internally to set the z index of the top thumb (see promoteThumb for details)
122488      */
122489     topZIndex: 10000,
122490     /**
122491      * @constructor
122492      * @cfg {Ext.slider.MultiSlider} slider The Slider to render to (required)
122493      */
122494     constructor: function(config) {
122495         var me = this;
122496         
122497         /**
122498          * @property slider
122499          * @type Ext.slider.MultiSlider
122500          * The slider this thumb is contained within
122501          */
122502         Ext.apply(me, config || {}, {
122503             cls: Ext.baseCSSPrefix + 'slider-thumb',
122504
122505             /**
122506              * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
122507              */
122508             constrain: false
122509         });
122510         me.callParent([config]);
122511
122512         if (me.slider.vertical) {
122513             Ext.apply(me, Ext.slider.Thumb.Vertical);
122514         }
122515     },
122516
122517     /**
122518      * Renders the thumb into a slider
122519      */
122520     render: function() {
122521         var me = this;
122522         
122523         me.el = me.slider.innerEl.insertFirst({cls: me.cls});
122524         if (me.disabled) {
122525             me.disable();
122526         }
122527         me.initEvents();
122528     },
122529     
122530     /**
122531      * @private
122532      * move the thumb
122533      */
122534     move: function(v, animate){
122535         if(!animate){
122536             this.el.setLeft(v);
122537         }else{
122538             Ext.create('Ext.fx.Anim', {
122539                 target: this.el,
122540                 duration: 350,
122541                 to: {
122542                     left: v
122543                 }
122544             });
122545         }
122546     },
122547
122548     /**
122549      * @private
122550      * Bring thumb dom element to front.
122551      */
122552     bringToFront: function() {
122553         this.el.setStyle('zIndex', this.topZIndex);
122554     },
122555     
122556     /**
122557      * @private
122558      * Send thumb dom element to back.
122559      */
122560     sendToBack: function() {
122561         this.el.setStyle('zIndex', '');
122562     },
122563     
122564     /**
122565      * Enables the thumb if it is currently disabled
122566      */
122567     enable: function() {
122568         var me = this;
122569         
122570         me.disabled = false;
122571         if (me.el) {
122572             me.el.removeCls(me.slider.disabledCls);
122573         }
122574     },
122575
122576     /**
122577      * Disables the thumb if it is currently enabled
122578      */
122579     disable: function() {
122580         var me = this;
122581         
122582         me.disabled = true;
122583         if (me.el) {
122584             me.el.addCls(me.slider.disabledCls);
122585         }
122586     },
122587
122588     /**
122589      * Sets up an Ext.dd.DragTracker for this thumb
122590      */
122591     initEvents: function() {
122592         var me = this,
122593             el = me.el;
122594
122595         me.tracker = Ext.create('Ext.dd.DragTracker', {
122596             onBeforeStart: Ext.Function.bind(me.onBeforeDragStart, me),
122597             onStart      : Ext.Function.bind(me.onDragStart, me),
122598             onDrag       : Ext.Function.bind(me.onDrag, me),
122599             onEnd        : Ext.Function.bind(me.onDragEnd, me),
122600             tolerance    : 3,
122601             autoStart    : 300,
122602             overCls      : Ext.baseCSSPrefix + 'slider-thumb-over'
122603         });
122604
122605         me.tracker.initEl(el);
122606     },
122607
122608     /**
122609      * @private
122610      * This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled,
122611      * this returns false to disable the DragTracker too.
122612      * @return {Boolean} False if the slider is currently disabled
122613      */
122614     onBeforeDragStart : function(e) {
122615         if (this.disabled) {
122616             return false;
122617         } else {
122618             this.slider.promoteThumb(this);
122619             return true;
122620         }
122621     },
122622
122623     /**
122624      * @private
122625      * This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class
122626      * to the thumb and fires the 'dragstart' event
122627      */
122628     onDragStart: function(e){
122629         var me = this;
122630         
122631         me.el.addCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
122632         me.dragging = true;
122633         me.dragStartValue = me.value;
122634
122635         me.slider.fireEvent('dragstart', me.slider, e, me);
122636     },
122637
122638     /**
122639      * @private
122640      * This is tied into the internal Ext.dd.DragTracker's onDrag template method. This is called every time
122641      * the DragTracker detects a drag movement. It updates the Slider's value using the position of the drag
122642      */
122643     onDrag: function(e) {
122644         var me       = this,
122645             slider   = me.slider,
122646             index    = me.index,
122647             newValue = me.getNewValue(),
122648             above,
122649             below;
122650
122651         if (me.constrain) {
122652             above = slider.thumbs[index + 1];
122653             below = slider.thumbs[index - 1];
122654
122655             if (below !== undefined && newValue <= below.value) {
122656                 newValue = below.value;
122657             }
122658             
122659             if (above !== undefined && newValue >= above.value) {
122660                 newValue = above.value;
122661             }
122662         }
122663
122664         slider.setValue(index, newValue, false);
122665         slider.fireEvent('drag', slider, e, me);
122666     },
122667
122668     getNewValue: function() {
122669         var slider = this.slider,
122670             pos = slider.innerEl.translatePoints(this.tracker.getXY());
122671
122672         return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
122673     },
122674
122675     /**
122676      * @private
122677      * This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and
122678      * fires the 'changecomplete' event with the new value
122679      */
122680     onDragEnd: function(e) {
122681         var me     = this,
122682             slider = me.slider,
122683             value  = me.value;
122684
122685         me.el.removeCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
122686
122687         me.dragging = false;
122688         slider.fireEvent('dragend', slider, e);
122689
122690         if (me.dragStartValue != value) {
122691             slider.fireEvent('changecomplete', slider, value, me);
122692         }
122693     },
122694
122695     destroy: function() {
122696         Ext.destroy(this.tracker);
122697     },
122698     statics: {
122699         // Method overrides to support vertical dragging of thumb within slider
122700         Vertical: {
122701             getNewValue: function() {
122702                 var slider   = this.slider,
122703                     innerEl  = slider.innerEl,
122704                     pos      = innerEl.translatePoints(this.tracker.getXY()),
122705                     bottom   = innerEl.getHeight() - pos.top;
122706
122707                 return Ext.util.Format.round(slider.reverseValue(bottom), slider.decimalPrecision);
122708             },
122709             move: function(v, animate) {
122710                 if (!animate) {
122711                     this.el.setBottom(v);
122712                 } else {
122713                     Ext.create('Ext.fx.Anim', {
122714                         target: this.el,
122715                         duration: 350,
122716                         to: {
122717                             bottom: v
122718                         }
122719                     });
122720                 }
122721             }
122722         }
122723     }
122724 });
122725
122726 /**
122727  * @class Ext.slider.Tip
122728  * @extends Ext.tip.Tip
122729  * Simple plugin for using an Ext.tip.Tip with a slider to show the slider value. In general this
122730  * class is not created directly, instead pass the {@link Ext.slider.Multi#useTips} and 
122731  * {@link Ext.slider.Multi#tipText} configuration options to the slider directly.
122732  * {@img Ext.slider.Tip/Ext.slider.Tip1.png Ext.slider.Tip component}
122733  * Example usage:
122734 <pre>
122735     Ext.create('Ext.slider.Single', {
122736         width: 214,
122737         minValue: 0,
122738         maxValue: 100,
122739         useTips: true,
122740         renderTo: Ext.getBody()
122741     });   
122742 </pre>
122743  * Optionally provide your own tip text by passing tipText:
122744  <pre>
122745  new Ext.slider.Single({
122746      width: 214,
122747      minValue: 0,
122748      maxValue: 100,
122749      useTips: true,
122750      tipText: function(thumb){
122751          return Ext.String.format('<b>{0}% complete</b>', thumb.value);
122752      }
122753  });
122754  </pre>
122755  * @xtype slidertip
122756  */
122757 Ext.define('Ext.slider.Tip', {
122758     extend: 'Ext.tip.Tip',
122759     minWidth: 10,
122760     alias: 'widget.slidertip',
122761     offsets : [0, -10],
122762     
122763     isSliderTip: true,
122764
122765     init: function(slider) {
122766         var me = this;
122767         
122768         slider.on({
122769             scope    : me,
122770             dragstart: me.onSlide,
122771             drag     : me.onSlide,
122772             dragend  : me.hide,
122773             destroy  : me.destroy
122774         });
122775     },
122776     /**
122777      * @private
122778      * Called whenever a dragstart or drag event is received on the associated Thumb. 
122779      * Aligns the Tip with the Thumb's new position.
122780      * @param {Ext.slider.MultiSlider} slider The slider
122781      * @param {Ext.EventObject} e The Event object
122782      * @param {Ext.slider.Thumb} thumb The thumb that the Tip is attached to
122783      */
122784     onSlide : function(slider, e, thumb) {
122785         var me = this;
122786         me.show();
122787         me.update(me.getText(thumb));
122788         me.doComponentLayout();
122789         me.el.alignTo(thumb.el, 'b-t?', me.offsets);
122790     },
122791
122792     /**
122793      * Used to create the text that appears in the Tip's body. By default this just returns
122794      * the value of the Slider Thumb that the Tip is attached to. Override to customize.
122795      * @param {Ext.slider.Thumb} thumb The Thumb that the Tip is attached to
122796      * @return {String} The text to display in the tip
122797      */
122798     getText : function(thumb) {
122799         return String(thumb.value);
122800     }
122801 });
122802 /**
122803  * @class Ext.slider.Multi
122804  * @extends Ext.form.field.Base
122805  * <p>Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis
122806  * clicking and animation. Can be added as an item to any container. In addition,  
122807  * {@img Ext.slider.Multi/Ext.slider.Multi.png Ext.slider.Multi component}
122808  * <p>Example usage:</p>
122809  * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
122810 <pre>
122811     Ext.create('Ext.slider.Multi', {
122812         width: 200,
122813         values: [25, 50, 75],
122814         increment: 5,
122815         minValue: 0,
122816         maxValue: 100,
122817
122818         //this defaults to true, setting to false allows the thumbs to pass each other
122819         {@link #constrainThumbs}: false,
122820         renderTo: Ext.getBody()
122821     });  
122822 </pre>
122823  * @xtype multislider
122824  */
122825 Ext.define('Ext.slider.Multi', {
122826     extend: 'Ext.form.field.Base',
122827     alias: 'widget.multislider',
122828     alternateClassName: 'Ext.slider.MultiSlider',
122829
122830     requires: [
122831         'Ext.slider.Thumb',
122832         'Ext.slider.Tip',
122833         'Ext.Number',
122834         'Ext.util.Format',
122835         'Ext.Template',
122836         'Ext.layout.component.field.Slider'
122837     ],
122838
122839     fieldSubTpl: [
122840         '<div class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
122841             '<div class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
122842                 '<div class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
122843                     '<a class="' + Ext.baseCSSPrefix + 'slider-focus" href="#" tabIndex="-1" hidefocus="on" role="presentation"></a>',
122844                 '</div>',
122845             '</div>',
122846         '</div>',
122847         {
122848             disableFormats: true,
122849             compiled: true
122850         }
122851     ],
122852
122853     /**
122854      * @cfg {Number} value
122855      * A value with which to initialize the slider. Defaults to minValue. Setting this will only
122856      * result in the creation of a single slider thumb; if you want multiple thumbs then use the
122857      * {@link #values} config instead.
122858      */
122859
122860     /**
122861      * @cfg {Array} values
122862      * Array of Number values with which to initalize the slider. A separate slider thumb will be created for
122863      * each value in this array. This will take precedence over the single {@link #value} config.
122864      */
122865
122866     /**
122867      * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false.
122868      */
122869     vertical: false,
122870     /**
122871      * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
122872      */
122873     minValue: 0,
122874     /**
122875      * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
122876      */
122877     maxValue: 100,
122878     /**
122879      * @cfg {Number/Boolean} decimalPrecision.
122880      * <p>The number of decimal places to which to round the Slider's value. Defaults to 0.</p>
122881      * <p>To disable rounding, configure as <tt><b>false</b></tt>.</p>
122882      */
122883     decimalPrecision: 0,
122884     /**
122885      * @cfg {Number} keyIncrement How many units to change the Slider when adjusting with keyboard navigation. Defaults to 1. If the increment config is larger, it will be used instead.
122886      */
122887     keyIncrement: 1,
122888     /**
122889      * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
122890      */
122891     increment: 0,
122892
122893     /**
122894      * @private
122895      * @property clickRange
122896      * @type Array
122897      * 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],
122898      * 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'
122899      * 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
122900      */
122901     clickRange: [5,15],
122902
122903     /**
122904      * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
122905      */
122906     clickToChange : true,
122907     /**
122908      * @cfg {Boolean} animate Turn on or off animation. Defaults to true
122909      */
122910     animate: true,
122911
122912     /**
122913      * True while the thumb is in a drag operation
122914      * @type Boolean
122915      */
122916     dragging: false,
122917
122918     /**
122919      * @cfg {Boolean} constrainThumbs True to disallow thumbs from overlapping one another. Defaults to true
122920      */
122921     constrainThumbs: true,
122922
122923     componentLayout: 'sliderfield',
122924
122925     /**
122926      * @cfg {Boolean} useTips
122927      * True to use an Ext.slider.Tip to display tips for the value. Defaults to <tt>true</tt>.
122928      */
122929     useTips : true,
122930
122931     /**
122932      * @cfg {Function} tipText
122933      * A function used to display custom text for the slider tip. Defaults to <tt>null</tt>, which will
122934      * use the default on the plugin.
122935      */
122936     tipText : null,
122937
122938     ariaRole: 'slider',
122939
122940     // private override
122941     initValue: function() {
122942         var me = this,
122943             extValue = Ext.value,
122944             // Fallback for initial values: values config -> value config -> minValue config -> 0
122945             values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
122946             i = 0,
122947             len = values.length;
122948
122949         // Store for use in dirty check
122950         me.originalValue = values;
122951
122952         // Add a thumb for each value
122953         for (; i < len; i++) {
122954             me.addThumb(values[i]);
122955         }
122956     },
122957
122958     // private override
122959     initComponent : function() {
122960         var me = this,
122961             tipPlug,
122962             hasTip;
122963         
122964         /**
122965          * @property thumbs
122966          * @type Array
122967          * Array containing references to each thumb
122968          */
122969         me.thumbs = [];
122970
122971         me.keyIncrement = Math.max(me.increment, me.keyIncrement);
122972
122973         me.addEvents(
122974             /**
122975              * @event beforechange
122976              * Fires before the slider value is changed. By returning false from an event handler,
122977              * you can cancel the event and prevent the slider from changing.
122978              * @param {Ext.slider.Multi} slider The slider
122979              * @param {Number} newValue The new value which the slider is being changed to.
122980              * @param {Number} oldValue The old value which the slider was previously.
122981              */
122982             'beforechange',
122983
122984             /**
122985              * @event change
122986              * Fires when the slider value is changed.
122987              * @param {Ext.slider.Multi} slider The slider
122988              * @param {Number} newValue The new value which the slider has been changed to.
122989              * @param {Ext.slider.Thumb} thumb The thumb that was changed
122990              */
122991             'change',
122992
122993             /**
122994              * @event changecomplete
122995              * Fires when the slider value is changed by the user and any drag operations have completed.
122996              * @param {Ext.slider.Multi} slider The slider
122997              * @param {Number} newValue The new value which the slider has been changed to.
122998              * @param {Ext.slider.Thumb} thumb The thumb that was changed
122999              */
123000             'changecomplete',
123001
123002             /**
123003              * @event dragstart
123004              * Fires after a drag operation has started.
123005              * @param {Ext.slider.Multi} slider The slider
123006              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
123007              */
123008             'dragstart',
123009
123010             /**
123011              * @event drag
123012              * Fires continuously during the drag operation while the mouse is moving.
123013              * @param {Ext.slider.Multi} slider The slider
123014              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
123015              */
123016             'drag',
123017
123018             /**
123019              * @event dragend
123020              * Fires after the drag operation has completed.
123021              * @param {Ext.slider.Multi} slider The slider
123022              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
123023              */
123024             'dragend'
123025         );
123026
123027         if (me.vertical) {
123028             Ext.apply(me, Ext.slider.Multi.Vertical);
123029         }
123030
123031         me.callParent();
123032
123033         // only can use it if it exists.
123034         if (me.useTips) {
123035             tipPlug = me.tipText ? {getText: me.tipText} : {};
123036             me.plugins = me.plugins || [];
123037             Ext.each(me.plugins, function(plug){
123038                 if (plug.isSliderTip) {
123039                     hasTip = true;
123040                     return false;
123041                 }
123042             });
123043             if (!hasTip) {
123044                 me.plugins.push(Ext.create('Ext.slider.Tip', tipPlug));
123045             }
123046         }
123047     },
123048
123049     /**
123050      * Creates a new thumb and adds it to the slider
123051      * @param {Number} value The initial value to set on the thumb. Defaults to 0
123052      * @return {Ext.slider.Thumb} The thumb
123053      */
123054     addThumb: function(value) {
123055         var me = this,
123056             thumb = Ext.create('Ext.slider.Thumb', {
123057             value    : value,
123058             slider   : me,
123059             index    : me.thumbs.length,
123060             constrain: me.constrainThumbs
123061         });
123062         me.thumbs.push(thumb);
123063
123064         //render the thumb now if needed
123065         if (me.rendered) {
123066             thumb.render();
123067         }
123068
123069         return thumb;
123070     },
123071
123072     /**
123073      * @private
123074      * Moves the given thumb above all other by increasing its z-index. This is called when as drag
123075      * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
123076      * required when the thumbs are stacked on top of each other at one of the ends of the slider's
123077      * range, which can result in the user not being able to move any of them.
123078      * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
123079      */
123080     promoteThumb: function(topThumb) {
123081         var thumbs = this.thumbs,
123082             ln = thumbs.length,
123083             zIndex, thumb, i;
123084             
123085         for (i = 0; i < ln; i++) {
123086             thumb = thumbs[i];
123087
123088             if (thumb == topThumb) {
123089                 thumb.bringToFront();
123090             } else {
123091                 thumb.sendToBack();
123092             }
123093         }
123094     },
123095
123096     // private override
123097     onRender : function() {
123098         var me = this,
123099             i = 0,
123100             thumbs = me.thumbs,
123101             len = thumbs.length,
123102             thumb;
123103
123104         Ext.applyIf(me.subTplData, {
123105             vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
123106             minValue: me.minValue,
123107             maxValue: me.maxValue,
123108             value: me.value
123109         });
123110
123111         Ext.applyIf(me.renderSelectors, {
123112             endEl: '.' + Ext.baseCSSPrefix + 'slider-end',
123113             innerEl: '.' + Ext.baseCSSPrefix + 'slider-inner',
123114             focusEl: '.' + Ext.baseCSSPrefix + 'slider-focus'
123115         });
123116
123117         me.callParent(arguments);
123118
123119         //render each thumb
123120         for (; i < len; i++) {
123121             thumbs[i].render();
123122         }
123123
123124         //calculate the size of half a thumb
123125         thumb = me.innerEl.down('.' + Ext.baseCSSPrefix + 'slider-thumb');
123126         me.halfThumb = (me.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
123127
123128     },
123129
123130     /**
123131      * Utility method to set the value of the field when the slider changes.
123132      * @param {Object} slider The slider object.
123133      * @param {Object} v The new value.
123134      * @private
123135      */
123136     onChange : function(slider, v) {
123137         this.setValue(v, undefined, true);
123138     },
123139
123140     /**
123141      * @private
123142      * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
123143      */
123144     initEvents : function() {
123145         var me = this;
123146         
123147         me.mon(me.el, {
123148             scope    : me,
123149             mousedown: me.onMouseDown,
123150             keydown  : me.onKeyDown,
123151             change : me.onChange
123152         });
123153
123154         me.focusEl.swallowEvent("click", true);
123155     },
123156
123157     /**
123158      * @private
123159      * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
123160      * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
123161      * @param {Ext.EventObject} e The click event
123162      */
123163     onMouseDown : function(e) {
123164         var me = this,
123165             thumbClicked = false,
123166             i = 0,
123167             thumbs = me.thumbs,
123168             len = thumbs.length,
123169             local;
123170             
123171         if (me.disabled) {
123172             return;
123173         }
123174
123175         //see if the click was on any of the thumbs
123176         for (; i < len; i++) {
123177             thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
123178         }
123179
123180         if (me.clickToChange && !thumbClicked) {
123181             local = me.innerEl.translatePoints(e.getXY());
123182             me.onClickChange(local);
123183         }
123184         me.focus();
123185     },
123186
123187     /**
123188      * @private
123189      * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Multi.Vertical.
123190      * Only changes the value if the click was within this.clickRange.
123191      * @param {Object} local Object containing top and left values for the click event.
123192      */
123193     onClickChange : function(local) {
123194         var me = this,
123195             thumb, index;
123196             
123197         if (local.top > me.clickRange[0] && local.top < me.clickRange[1]) {
123198             //find the nearest thumb to the click event
123199             thumb = me.getNearest(local, 'left');
123200             if (!thumb.disabled) {
123201                 index = thumb.index;
123202                 me.setValue(index, Ext.util.Format.round(me.reverseValue(local.left), me.decimalPrecision), undefined, true);
123203             }
123204         }
123205     },
123206
123207     /**
123208      * @private
123209      * Returns the nearest thumb to a click event, along with its distance
123210      * @param {Object} local Object containing top and left values from a click event
123211      * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
123212      * @return {Object} The closest thumb object and its distance from the click event
123213      */
123214     getNearest: function(local, prop) {
123215         var me = this,
123216             localValue = prop == 'top' ? me.innerEl.getHeight() - local[prop] : local[prop],
123217             clickValue = me.reverseValue(localValue),
123218             nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
123219             index = 0,
123220             nearest = null,
123221             thumbs = me.thumbs,
123222             i = 0,
123223             len = thumbs.length,
123224             thumb,
123225             value,
123226             dist;
123227
123228         for (; i < len; i++) {
123229             thumb = me.thumbs[i];
123230             value = thumb.value;
123231             dist  = Math.abs(value - clickValue);
123232
123233             if (Math.abs(dist <= nearestDistance)) {
123234                 nearest = thumb;
123235                 index = i;
123236                 nearestDistance = dist;
123237             }
123238         }
123239         return nearest;
123240     },
123241
123242     /**
123243      * @private
123244      * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
123245      * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
123246      * @param {Ext.EventObject} e The Event object
123247      */
123248     onKeyDown : function(e) {
123249         /*
123250          * The behaviour for keyboard handling with multiple thumbs is currently undefined.
123251          * There's no real sane default for it, so leave it like this until we come up
123252          * with a better way of doing it.
123253          */
123254         var me = this,
123255             k,
123256             val;
123257         
123258         if(me.disabled || me.thumbs.length !== 1) {
123259             e.preventDefault();
123260             return;
123261         }
123262         k = e.getKey();
123263         
123264         switch(k) {
123265             case e.UP:
123266             case e.RIGHT:
123267                 e.stopEvent();
123268                 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
123269                 me.setValue(0, val, undefined, true);
123270             break;
123271             case e.DOWN:
123272             case e.LEFT:
123273                 e.stopEvent();
123274                 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
123275                 me.setValue(0, val, undefined, true);
123276             break;
123277             default:
123278                 e.preventDefault();
123279         }
123280     },
123281
123282     /**
123283      * @private
123284      * If using snapping, this takes a desired new value and returns the closest snapped
123285      * value to it
123286      * @param {Number} value The unsnapped value
123287      * @return {Number} The value of the nearest snap target
123288      */
123289     doSnap : function(value) {
123290         var newValue = value,
123291             inc = this.increment,
123292             m;
123293             
123294         if (!(inc && value)) {
123295             return value;
123296         }
123297         m = value % inc;
123298         if (m !== 0) {
123299             newValue -= m;
123300             if (m * 2 >= inc) {
123301                 newValue += inc;
123302             } else if (m * 2 < -inc) {
123303                 newValue -= inc;
123304             }
123305         }
123306         return Ext.Number.constrain(newValue, this.minValue,  this.maxValue);
123307     },
123308
123309     // private
123310     afterRender : function() {
123311         var me = this,
123312             i = 0,
123313             thumbs = me.thumbs,
123314             len = thumbs.length,
123315             thumb,
123316             v;
123317             
123318         me.callParent(arguments);
123319
123320         for (; i < len; i++) {
123321             thumb = thumbs[i];
123322
123323             if (thumb.value !== undefined) {
123324                 v = me.normalizeValue(thumb.value);
123325                 if (v !== thumb.value) {
123326                     // delete this.value;
123327                     me.setValue(i, v, false);
123328                 } else {
123329                     thumb.move(me.translateValue(v), false);
123330                 }
123331             }
123332         }
123333     },
123334
123335     /**
123336      * @private
123337      * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
123338      * the ratio is 2
123339      * @return {Number} The ratio of pixels to mapped values
123340      */
123341     getRatio : function() {
123342         var w = this.innerEl.getWidth(),
123343             v = this.maxValue - this.minValue;
123344         return v === 0 ? w : (w/v);
123345     },
123346
123347     /**
123348      * @private
123349      * Returns a snapped, constrained value when given a desired value
123350      * @param {Number} value Raw number value
123351      * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
123352      */
123353     normalizeValue : function(v) {
123354         var me = this;
123355         
123356         v = me.doSnap(v);
123357         v = Ext.util.Format.round(v, me.decimalPrecision);
123358         v = Ext.Number.constrain(v, me.minValue, me.maxValue);
123359         return v;
123360     },
123361
123362     /**
123363      * Sets the minimum value for the slider instance. If the current value is less than the
123364      * minimum value, the current value will be changed.
123365      * @param {Number} val The new minimum value
123366      */
123367     setMinValue : function(val) {
123368         var me = this,
123369             i = 0,
123370             thumbs = me.thumbs,
123371             len = thumbs.length,
123372             t;
123373             
123374         me.minValue = val;
123375         me.inputEl.dom.setAttribute('aria-valuemin', val);
123376
123377         for (; i < len; ++i) {
123378             t = thumbs[i];
123379             t.value = t.value < val ? val : t.value;
123380         }
123381         me.syncThumbs();
123382     },
123383
123384     /**
123385      * Sets the maximum value for the slider instance. If the current value is more than the
123386      * maximum value, the current value will be changed.
123387      * @param {Number} val The new maximum value
123388      */
123389     setMaxValue : function(val) {
123390         var me = this,
123391             i = 0,
123392             thumbs = me.thumbs,
123393             len = thumbs.length,
123394             t;
123395             
123396         me.maxValue = val;
123397         me.inputEl.dom.setAttribute('aria-valuemax', val);
123398
123399         for (; i < len; ++i) {
123400             t = thumbs[i];
123401             t.value = t.value > val ? val : t.value;
123402         }
123403         me.syncThumbs();
123404     },
123405
123406     /**
123407      * Programmatically sets the value of the Slider. Ensures that the value is constrained within
123408      * the minValue and maxValue.
123409      * @param {Number} index Index of the thumb to move
123410      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
123411      * @param {Boolean} animate Turn on or off animation, defaults to true
123412      */
123413     setValue : function(index, value, animate, changeComplete) {
123414         var me = this,
123415             thumb = me.thumbs[index];
123416
123417         // ensures value is contstrained and snapped
123418         value = me.normalizeValue(value);
123419
123420         if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
123421             thumb.value = value;
123422             if (me.rendered) {
123423                 // TODO this only handles a single value; need a solution for exposing multiple values to aria.
123424                 // Perhaps this should go on each thumb element rather than the outer element.
123425                 me.inputEl.set({
123426                     'aria-valuenow': value,
123427                     'aria-valuetext': value
123428                 });
123429
123430                 thumb.move(me.translateValue(value), Ext.isDefined(animate) ? animate !== false : me.animate);
123431
123432                 me.fireEvent('change', me, value, thumb);
123433                 if (changeComplete) {
123434                     me.fireEvent('changecomplete', me, value, thumb);
123435                 }
123436             }
123437         }
123438     },
123439
123440     /**
123441      * @private
123442      */
123443     translateValue : function(v) {
123444         var ratio = this.getRatio();
123445         return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
123446     },
123447
123448     /**
123449      * @private
123450      * Given a pixel location along the slider, returns the mapped slider value for that pixel.
123451      * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
123452      * returns 200
123453      * @param {Number} pos The position along the slider to return a mapped value for
123454      * @return {Number} The mapped value for the given position
123455      */
123456     reverseValue : function(pos) {
123457         var ratio = this.getRatio();
123458         return (pos + (this.minValue * ratio)) / ratio;
123459     },
123460
123461     // private
123462     focus : function() {
123463         this.focusEl.focus(10);
123464     },
123465
123466     //private
123467     onDisable: function() {
123468         var me = this,
123469             i = 0,
123470             thumbs = me.thumbs,
123471             len = thumbs.length,
123472             thumb,
123473             el,
123474             xy;
123475             
123476         me.callParent();
123477
123478         for (; i < len; i++) {
123479             thumb = thumbs[i];
123480             el = thumb.el;
123481
123482             thumb.disable();
123483
123484             if(Ext.isIE) {
123485                 //IE breaks when using overflow visible and opacity other than 1.
123486                 //Create a place holder for the thumb and display it.
123487                 xy = el.getXY();
123488                 el.hide();
123489
123490                 me.innerEl.addCls(me.disabledCls).dom.disabled = true;
123491
123492                 if (!me.thumbHolder) {
123493                     me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
123494                 }
123495
123496                 me.thumbHolder.show().setXY(xy);
123497             }
123498         }
123499     },
123500
123501     //private
123502     onEnable: function() {
123503         var me = this,
123504             i = 0,
123505             thumbs = me.thumbs,
123506             len = thumbs.length,
123507             thumb,
123508             el;
123509             
123510         this.callParent();
123511
123512         for (; i < len; i++) {
123513             thumb = thumbs[i];
123514             el = thumb.el;
123515
123516             thumb.enable();
123517
123518             if (Ext.isIE) {
123519                 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
123520
123521                 if (me.thumbHolder) {
123522                     me.thumbHolder.hide();
123523                 }
123524
123525                 el.show();
123526                 me.syncThumbs();
123527             }
123528         }
123529     },
123530
123531     /**
123532      * Synchronizes thumbs position to the proper proportion of the total component width based
123533      * on the current slider {@link #value}.  This will be called automatically when the Slider
123534      * is resized by a layout, but if it is rendered auto width, this method can be called from
123535      * another resize handler to sync the Slider if necessary.
123536      */
123537     syncThumbs : function() {
123538         if (this.rendered) {
123539             var thumbs = this.thumbs,
123540                 length = thumbs.length,
123541                 i = 0;
123542
123543             for (; i < length; i++) {
123544                 thumbs[i].move(this.translateValue(thumbs[i].value));
123545             }
123546         }
123547     },
123548
123549     /**
123550      * Returns the current value of the slider
123551      * @param {Number} index The index of the thumb to return a value for
123552      * @return {Number/Array} The current value of the slider at the given index, or an array of
123553      * all thumb values if no index is given.
123554      */
123555     getValue : function(index) {
123556         return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
123557     },
123558
123559     /**
123560      * Returns an array of values - one for the location of each thumb
123561      * @return {Array} The set of thumb values
123562      */
123563     getValues: function() {
123564         var values = [],
123565             i = 0,
123566             thumbs = this.thumbs,
123567             len = thumbs.length;
123568
123569         for (; i < len; i++) {
123570             values.push(thumbs[i].value);
123571         }
123572
123573         return values;
123574     },
123575
123576     getSubmitValue: function() {
123577         var me = this;
123578         return (me.disabled || !me.submitValue) ? null : me.getValue();
123579     },
123580
123581     reset: function() {
123582         var me = this,
123583             Array = Ext.Array;
123584         Array.forEach(Array.from(me.originalValue), function(val, i) {
123585             me.setValue(i, val);
123586         });
123587         me.clearInvalid();
123588         // delete here so we reset back to the original state
123589         delete me.wasValid;
123590     },
123591
123592     // private
123593     beforeDestroy : function() {
123594         var me = this;
123595         
123596         Ext.destroyMembers(me.innerEl, me.endEl, me.focusEl);
123597         Ext.each(me.thumbs, function(thumb) {
123598             Ext.destroy(thumb);
123599         }, me);
123600
123601         me.callParent();
123602     },
123603
123604     statics: {
123605         // Method overrides to support slider with vertical orientation
123606         Vertical: {
123607             getRatio : function() {
123608                 var h = this.innerEl.getHeight(),
123609                     v = this.maxValue - this.minValue;
123610                 return h/v;
123611             },
123612
123613             onClickChange : function(local) {
123614                 var me = this,
123615                     thumb, index, bottom;
123616
123617                 if (local.left > me.clickRange[0] && local.left < me.clickRange[1]) {
123618                     thumb = me.getNearest(local, 'top');
123619                     if (!thumb.disabled) {
123620                         index = thumb.index;
123621                         bottom =  me.reverseValue(me.innerEl.getHeight() - local.top);
123622
123623                         me.setValue(index, Ext.util.Format.round(me.minValue + bottom, me.decimalPrecision), undefined, true);
123624                     }
123625                 }
123626             }
123627         }
123628     }
123629 });
123630
123631 /**
123632  * @class Ext.slider.Single
123633  * @extends Ext.slider.Multi
123634  * Slider which supports vertical or horizontal orientation, keyboard adjustments,
123635  * configurable snapping, axis clicking and animation. Can be added as an item to
123636  * any container. 
123637  * {@img Ext.slider.Single/Ext.slider.Single.png Ext.slider.Single component}
123638  * Example usage:
123639 <pre><code>
123640     Ext.create('Ext.slider.Single', {
123641         width: 200,
123642         value: 50,
123643         increment: 10,
123644         minValue: 0,
123645         maxValue: 100,
123646         renderTo: Ext.getBody()
123647     });
123648 </code></pre>
123649  * The class Ext.slider.Single is aliased to Ext.Slider for backwards compatibility.
123650  * @xtype slider
123651  */
123652 Ext.define('Ext.slider.Single', {
123653     extend: 'Ext.slider.Multi',
123654     alias: ['widget.slider', 'widget.sliderfield'],
123655     alternateClassName: ['Ext.Slider', 'Ext.form.SliderField', 'Ext.slider.SingleSlider', 'Ext.slider.Slider'],
123656
123657     /**
123658      * Returns the current value of the slider
123659      * @return {Number} The current value of the slider
123660      */
123661     getValue: function() {
123662         //just returns the value of the first thumb, which should be the only one in a single slider
123663         return this.callParent([0]);
123664     },
123665
123666     /**
123667      * Programmatically sets the value of the Slider. Ensures that the value is constrained within
123668      * the minValue and maxValue.
123669      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
123670      * @param {Boolean} animate Turn on or off animation, defaults to true
123671      */
123672     setValue: function(value, animate) {
123673         var args = Ext.toArray(arguments),
123674             len  = args.length;
123675
123676         //this is to maintain backwards compatiblity for sliders with only one thunb. Usually you must pass the thumb
123677         //index to setValue, but if we only have one thumb we inject the index here first if given the multi-slider
123678         //signature without the required index. The index will always be 0 for a single slider
123679         if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) {
123680             args.unshift(0);
123681         }
123682
123683         return this.callParent(args);
123684     },
123685
123686     // private
123687     getNearest : function(){
123688         // Since there's only 1 thumb, it's always the nearest
123689         return this.thumbs[0];
123690     }
123691 });
123692
123693 /**
123694  * @author Ed Spencer
123695  * @class Ext.tab.Tab
123696  * @extends Ext.button.Button
123697  * 
123698  * <p>Represents a single Tab in a {@link Ext.tab.Panel TabPanel}. A Tab is simply a slightly customized {@link Ext.button.Button Button}, 
123699  * styled to look like a tab. Tabs are optionally closable, and can also be disabled. 99% of the time you will not
123700  * need to create Tabs manually as the framework does so automatically when you use a {@link Ext.tab.Panel TabPanel}</p>
123701  *
123702  * @xtype tab
123703  */
123704 Ext.define('Ext.tab.Tab', {
123705     extend: 'Ext.button.Button',
123706     alias: 'widget.tab',
123707     
123708     requires: [
123709         'Ext.layout.component.Tab',
123710         'Ext.util.KeyNav'
123711     ],
123712
123713     componentLayout: 'tab',
123714
123715     isTab: true,
123716
123717     baseCls: Ext.baseCSSPrefix + 'tab',
123718
123719     /**
123720      * @cfg {String} activeCls
123721      * The CSS class to be applied to a Tab when it is active. Defaults to 'x-tab-active'.
123722      * Providing your own CSS for this class enables you to customize the active state.
123723      */
123724     activeCls: 'active',
123725     
123726     /**
123727      * @cfg {String} disabledCls
123728      * The CSS class to be applied to a Tab when it is disabled. Defaults to 'x-tab-disabled'.
123729      */
123730
123731     /**
123732      * @cfg {String} closableCls
123733      * The CSS class which is added to the tab when it is closable
123734      */
123735     closableCls: 'closable',
123736
123737     /**
123738      * @cfg {Boolean} closable True to make the Tab start closable (the close icon will be visible). Defaults to true
123739      */
123740     closable: true,
123741
123742     /**
123743      * @cfg {String} closeText 
123744      * The accessible text label for the close button link; only used when {@link #closable} = true.
123745      * Defaults to 'Close Tab'.
123746      */
123747     closeText: 'Close Tab',
123748
123749     /**
123750      * @property Boolean
123751      * Read-only property indicating that this tab is currently active. This is NOT a public configuration.
123752      */
123753     active: false,
123754
123755     /**
123756      * @property closable
123757      * @type Boolean
123758      * True if the tab is currently closable
123759      */
123760
123761     scale: false,
123762
123763     position: 'top',
123764     
123765     initComponent: function() {
123766         var me = this;
123767
123768         me.addEvents(
123769             /**
123770              * @event activate
123771              * @param {Ext.tab.Tab} this
123772              */
123773             'activate',
123774
123775             /**
123776              * @event deactivate
123777              * @param {Ext.tab.Tab} this
123778              */
123779             'deactivate',
123780
123781             /**
123782              * @event beforeclose
123783              * Fires if the user clicks on the Tab's close button, but before the {@link #close} event is fired. Return
123784              * false from any listener to stop the close event being fired
123785              * @param {Ext.tab.Tab} tab The Tab object
123786              */
123787             'beforeclose',
123788
123789             /**
123790              * @event beforeclose
123791              * Fires to indicate that the tab is to be closed, usually because the user has clicked the close button.
123792              * @param {Ext.tab.Tab} tab The Tab object
123793              */
123794             'close'
123795         );
123796         
123797         me.callParent(arguments);
123798
123799         if (me.card) {
123800             me.setCard(me.card);
123801         }
123802     },
123803
123804     /**
123805      * @ignore
123806      */
123807     onRender: function() {
123808         var me = this;
123809         
123810         me.addClsWithUI(me.position);
123811         
123812         // Set all the state classNames, as they need to include the UI
123813         // me.disabledCls = me.getClsWithUIs('disabled');
123814
123815         me.syncClosableUI();
123816
123817         me.callParent(arguments);
123818         
123819         if (me.active) {
123820             me.activate(true);
123821         }
123822
123823         me.syncClosableElements();
123824         
123825         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
123826             enter: me.onEnterKey,
123827             del: me.onDeleteKey,
123828             scope: me
123829         });
123830     },
123831     
123832     // inherit docs
123833     enable : function(silent) {
123834         var me = this;
123835
123836         me.callParent(arguments);
123837         
123838         me.removeClsWithUI(me.position + '-disabled');
123839
123840         return me;
123841     },
123842
123843     // inherit docs
123844     disable : function(silent) {
123845         var me = this;
123846         
123847         me.callParent(arguments);
123848         
123849         me.addClsWithUI(me.position + '-disabled');
123850
123851         return me;
123852     },
123853     
123854     /**
123855      * @ignore
123856      */
123857     onDestroy: function() {
123858         var me = this;
123859
123860         if (me.closeEl) {
123861             me.closeEl.un('click', Ext.EventManager.preventDefault);
123862             me.closeEl = null;
123863         }
123864
123865         Ext.destroy(me.keyNav);
123866         delete me.keyNav;
123867
123868         me.callParent(arguments);
123869     },
123870
123871     /**
123872      * Sets the tab as either closable or not
123873      * @param {Boolean} closable Pass false to make the tab not closable. Otherwise the tab will be made closable (eg a
123874      * close button will appear on the tab)
123875      */
123876     setClosable: function(closable) {
123877         var me = this;
123878
123879         // Closable must be true if no args
123880         closable = (!arguments.length || !!closable);
123881
123882         if (me.closable != closable) {
123883             me.closable = closable;
123884
123885             // set property on the user-facing item ('card'):
123886             if (me.card) {
123887                 me.card.closable = closable;
123888             }
123889
123890             me.syncClosableUI();
123891
123892             if (me.rendered) {
123893                 me.syncClosableElements();
123894
123895                 // Tab will change width to accommodate close icon
123896                 me.doComponentLayout();
123897                 if (me.ownerCt) {
123898                     me.ownerCt.doLayout();
123899                 }
123900             }
123901         }
123902     },
123903
123904     /**
123905      * This method ensures that the closeBtn element exists or not based on 'closable'.
123906      * @private
123907      */
123908     syncClosableElements: function () {
123909         var me = this;
123910
123911         if (me.closable) {
123912             if (!me.closeEl) {
123913                 me.closeEl = me.el.createChild({
123914                     tag: 'a',
123915                     cls: me.baseCls + '-close-btn',
123916                     href: '#',
123917                     html: me.closeText,
123918                     title: me.closeText
123919                 }).on('click', Ext.EventManager.preventDefault);  // mon ???
123920             }
123921         } else {
123922             var closeEl = me.closeEl;
123923             if (closeEl) {
123924                 closeEl.un('click', Ext.EventManager.preventDefault);
123925                 closeEl.remove();
123926                 me.closeEl = null;
123927             }
123928         }
123929     },
123930
123931     /**
123932      * This method ensures that the UI classes are added or removed based on 'closable'.
123933      * @private
123934      */
123935     syncClosableUI: function () {
123936         var me = this, classes = [me.closableCls, me.closableCls + '-' + me.position];
123937
123938         if (me.closable) {
123939             me.addClsWithUI(classes);
123940         } else {
123941             me.removeClsWithUI(classes);
123942         }
123943     },
123944
123945     /**
123946      * Sets this tab's attached card. Usually this is handled automatically by the {@link Ext.tab.Panel} that this Tab
123947      * belongs to and would not need to be done by the developer
123948      * @param {Ext.Component} card The card to set
123949      */
123950     setCard: function(card) {
123951         var me = this;
123952
123953         me.card = card;
123954         me.setText(me.title || card.title);
123955         me.setIconCls(me.iconCls || card.iconCls);
123956     },
123957
123958     /**
123959      * @private
123960      * Listener attached to click events on the Tab's close button
123961      */
123962     onCloseClick: function() {
123963         var me = this;
123964
123965         if (me.fireEvent('beforeclose', me) !== false) {
123966             if (me.tabBar) {
123967                 if (me.tabBar.closeTab(me) === false) {
123968                     // beforeclose on the panel vetoed the event, stop here
123969                     return;
123970                 }
123971             } else {
123972                 // if there's no tabbar, fire the close event
123973                 me.fireEvent('close', me);
123974             }
123975         }
123976     },
123977     
123978     /**
123979      * Fires the close event on the tab.
123980      * @private
123981      */
123982     fireClose: function(){
123983         this.fireEvent('close', this);
123984     },
123985     
123986     /**
123987      * @private
123988      */
123989     onEnterKey: function(e) {
123990         var me = this;
123991         
123992         if (me.tabBar) {
123993             me.tabBar.onClick(e, me.el);
123994         }
123995     },
123996     
123997    /**
123998      * @private
123999      */
124000     onDeleteKey: function(e) {
124001         var me = this;
124002         
124003         if (me.closable) {
124004             me.onCloseClick();
124005         }
124006     },
124007     
124008     // @private
124009     activate : function(supressEvent) {
124010         var me = this;
124011         
124012         me.active = true;
124013         me.addClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
124014
124015         if (supressEvent !== true) {
124016             me.fireEvent('activate', me);
124017         }
124018     },
124019
124020     // @private
124021     deactivate : function(supressEvent) {
124022         var me = this;
124023         
124024         me.active = false;
124025         me.removeClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
124026         
124027         if (supressEvent !== true) {
124028             me.fireEvent('deactivate', me);
124029         }
124030     }
124031 });
124032
124033 /**
124034  * @author Ed Spencer
124035  * @class Ext.tab.Bar
124036  * @extends Ext.panel.Header
124037  * <p>TabBar is used internally by a {@link Ext.tab.Panel TabPanel} and wouldn't usually need to be created manually.</p>
124038  *
124039  * @xtype tabbar
124040  */
124041 Ext.define('Ext.tab.Bar', {
124042     extend: 'Ext.panel.Header',
124043     alias: 'widget.tabbar',
124044     baseCls: Ext.baseCSSPrefix + 'tab-bar',
124045
124046     requires: [
124047         'Ext.tab.Tab',
124048         'Ext.FocusManager'
124049     ],
124050
124051     // @private
124052     defaultType: 'tab',
124053
124054     /**
124055      * @cfg Boolean plain
124056      * True to not show the full background on the tabbar
124057      */
124058     plain: false,
124059
124060     // @private
124061     renderTpl: [
124062         '<div class="{baseCls}-body<tpl if="ui"> {baseCls}-body-{ui}<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl></tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>',
124063         '<div class="{baseCls}-strip<tpl if="ui"> {baseCls}-strip-{ui}<tpl for="uiCls"> {parent.baseCls}-strip-{parent.ui}-{.}</tpl></tpl>"></div>'
124064     ],
124065
124066     /**
124067      * @cfg {Number} minTabWidth The minimum width for each tab. Defaults to <tt>30</tt>.
124068      */
124069     minTabWidth: 30,
124070
124071     /**
124072      * @cfg {Number} maxTabWidth The maximum width for each tab. Defaults to <tt>undefined</tt>.
124073      */
124074     maxTabWidth: undefined,
124075
124076     // @private
124077     initComponent: function() {
124078         var me = this,
124079             keys;
124080
124081         if (me.plain) {
124082             me.setUI(me.ui + '-plain');
124083         }
124084         
124085         me.addClsWithUI(me.dock);
124086
124087         me.addEvents(
124088             /**
124089              * @event change
124090              * Fired when the currently-active tab has changed
124091              * @param {Ext.tab.Bar} tabBar The TabBar
124092              * @param {Ext.Tab} tab The new Tab
124093              * @param {Ext.Component} card The card that was just shown in the TabPanel
124094              */
124095             'change'
124096         );
124097
124098         Ext.applyIf(me.renderSelectors, {
124099             body : '.' + me.baseCls + '-body',
124100             strip: '.' + me.baseCls + '-strip'
124101         });
124102         me.callParent(arguments);
124103
124104         // TabBar must override the Header's align setting.
124105         me.layout.align = (me.orientation == 'vertical') ? 'left' : 'top';
124106         me.layout.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.Scroller', me.layout);
124107         me.items.removeAt(me.items.getCount() - 1);
124108         me.items.removeAt(me.items.getCount() - 1);
124109         
124110         // Subscribe to Ext.FocusManager for key navigation
124111         keys = me.orientation == 'vertical' ? ['up', 'down'] : ['left', 'right'];
124112         Ext.FocusManager.subscribe(me, {
124113             keys: keys
124114         });
124115     },
124116
124117     // @private
124118     onAdd: function(tab) {
124119         var me = this,
124120             tabPanel = me.tabPanel,
124121             hasOwner = !!tabPanel;
124122
124123         me.callParent(arguments);
124124         tab.position = me.dock;
124125         if (hasOwner) {
124126             tab.minWidth = tabPanel.minTabWidth;
124127         }
124128         else {
124129             tab.minWidth = me.minTabWidth + (tab.iconCls ? 25 : 0);
124130         }
124131         tab.maxWidth = me.maxTabWidth || (hasOwner ? tabPanel.maxTabWidth : undefined);
124132     },
124133
124134     // @private
124135     afterRender: function() {
124136         var me = this;
124137
124138         me.mon(me.el, {
124139             scope: me,
124140             click: me.onClick,
124141             delegate: '.' + Ext.baseCSSPrefix + 'tab'
124142         });
124143         me.callParent(arguments);
124144         
124145     },
124146
124147     afterComponentLayout : function() {
124148         var me = this;
124149         
124150         me.callParent(arguments);
124151         me.strip.setWidth(me.el.getWidth());
124152     },
124153
124154     // @private
124155     onClick: function(e, target) {
124156         // The target might not be a valid tab el.
124157         var tab = Ext.getCmp(target.id),
124158             tabPanel = this.tabPanel,
124159             allowActive = true;
124160
124161         target = e.getTarget();
124162
124163         if (tab && tab.isDisabled && !tab.isDisabled()) {
124164             if (tab.closable && target === tab.closeEl.dom) {
124165                 tab.onCloseClick();
124166             } else {
124167                 if (tabPanel) {
124168                     // TabPanel will card setActiveTab of the TabBar
124169                     tabPanel.setActiveTab(tab.card);
124170                 } else {
124171                     this.setActiveTab(tab);
124172                 }
124173                 tab.focus();
124174             }
124175         }
124176     },
124177
124178     /**
124179      * @private
124180      * Closes the given tab by removing it from the TabBar and removing the corresponding card from the TabPanel
124181      * @param {Ext.Tab} tab The tab to close
124182      */
124183     closeTab: function(tab) {
124184         var me = this,
124185             card = tab.card,
124186             tabPanel = me.tabPanel,
124187             nextTab;
124188             
124189         if (card && card.fireEvent('beforeclose', card) === false) {
124190             return false;
124191         }
124192
124193         if (tab.active && me.items.getCount() > 1) {
124194             nextTab = tab.next('tab') || me.items.items[0];
124195             me.setActiveTab(nextTab);
124196             if (tabPanel) {
124197                 tabPanel.setActiveTab(nextTab.card);
124198             }
124199         }
124200         /*
124201          * force the close event to fire. By the time this function returns,
124202          * the tab is already destroyed and all listeners have been purged
124203          * so the tab can't fire itself.
124204          */
124205         tab.fireClose();
124206         me.remove(tab);
124207
124208         if (tabPanel && card) {
124209             card.fireEvent('close', card);
124210             tabPanel.remove(card);
124211         }
124212         
124213         if (nextTab) {
124214             nextTab.focus();
124215         }
124216     },
124217
124218     /**
124219      * @private
124220      * Marks the given tab as active
124221      * @param {Ext.Tab} tab The tab to mark active
124222      */
124223     setActiveTab: function(tab) {
124224         if (tab.disabled) {
124225             return;
124226         }
124227         var me = this;
124228         if (me.activeTab) {
124229             me.activeTab.deactivate();
124230         }
124231         tab.activate();
124232         
124233         if (me.rendered) {
124234             me.layout.layout();
124235             tab.el.scrollIntoView(me.layout.getRenderTarget());
124236         }
124237         me.activeTab = tab;
124238         me.fireEvent('change', me, tab, tab.card);
124239     }
124240 });
124241 /**
124242  * @author Ed Spencer, Tommy Maintz, Brian Moeskau
124243  * @class Ext.tab.Panel
124244  * @extends Ext.panel.Panel
124245
124246 A basic tab container. TabPanels can be used exactly like a standard {@link Ext.panel.Panel} for layout purposes, but also 
124247 have special support for containing child Components (`{@link Ext.container.Container#items items}`) that are managed 
124248 using a {@link Ext.layout.container.Card CardLayout layout manager}, and displayed as separate tabs.
124249
124250 __Note:__
124251
124252 By default, a tab's close tool _destroys_ the child tab Component and all its descendants. This makes the child tab 
124253 Component, and all its descendants __unusable__. To enable re-use of a tab, configure the TabPanel with `{@link #autoDestroy autoDestroy: false}`.
124254
124255 __TabPanel's layout:__
124256
124257 TabPanels use a Dock layout to position the {@link Ext.tab.Bar TabBar} at the top of the widget. Panels added to the TabPanel will have their 
124258 header hidden by default because the Tab will automatically take the Panel's configured title and icon.
124259
124260 TabPanels use their {@link Ext.panel.Panel#header header} or {@link Ext.panel.Panel#footer footer} element (depending on the {@link #tabPosition} 
124261 configuration) to accommodate the tab selector buttons. This means that a TabPanel will not display any configured title, and will not display any 
124262 configured header {@link Ext.panel.Panel#tools tools}.
124263
124264 To display a header, embed the TabPanel in a {@link Ext.panel.Panel Panel} which uses `{@link Ext.container.Container#layout layout:'fit'}`.
124265
124266 __Examples:__
124267
124268 Here is a basic TabPanel rendered to the body. This also shows the useful configuration {@link #activeTab}, which allows you to set the active tab on render. 
124269 If you do not set an {@link #activeTab}, no tabs will be active by default.
124270 {@img Ext.tab.Panel/Ext.tab.Panel1.png TabPanel component}
124271 Example usage:
124272
124273     Ext.create('Ext.tab.Panel', {
124274         width: 300,
124275         height: 200,
124276         activeTab: 0,
124277         items: [
124278             {
124279                 title: 'Tab 1',
124280                 bodyPadding: 10,
124281                 html : 'A simple tab'
124282             },
124283             {
124284                 title: 'Tab 2',
124285                 html : 'Another one'
124286             }
124287         ],
124288         renderTo : Ext.getBody()
124289     }); 
124290     
124291 It is easy to control the visibility of items in the tab bar. Specify hidden: true to have the
124292 tab button hidden initially. Items can be subsequently hidden and show by accessing the
124293 tab property on the child item.
124294
124295 Example usage:
124296     
124297     var tabs = Ext.create('Ext.tab.Panel', {
124298         width: 400,
124299         height: 400,
124300         renderTo: document.body,
124301         items: [{
124302             title: 'Home',
124303             html: 'Home',
124304             itemId: 'home'
124305         }, {
124306             title: 'Users',
124307             html: 'Users',
124308             itemId: 'users',
124309             hidden: true
124310         }, {
124311             title: 'Tickets',
124312             html: 'Tickets',
124313             itemId: 'tickets'
124314         }]    
124315     });
124316     
124317     setTimeout(function(){
124318         tabs.child('#home').tab.hide();
124319         var users = tabs.child('#users');
124320         users.tab.show();
124321         tabs.setActiveTab(users);
124322     }, 1000);
124323
124324 You can remove the background of the TabBar by setting the {@link #plain} property to `false`.
124325
124326 Example usage:
124327
124328     Ext.create('Ext.tab.Panel', {
124329         width: 300,
124330         height: 200,
124331         activeTab: 0,
124332         plain: true,
124333         items: [
124334             {
124335                 title: 'Tab 1',
124336                 bodyPadding: 10,
124337                 html : 'A simple tab'
124338             },
124339             {
124340                 title: 'Tab 2',
124341                 html : 'Another one'
124342             }
124343         ],
124344         renderTo : Ext.getBody()
124345     }); 
124346
124347 Another useful configuration of TabPanel is {@link #tabPosition}. This allows you to change the position where the tabs are displayed. The available 
124348 options for this are `'top'` (default) and `'bottom'`.
124349 {@img Ext.tab.Panel/Ext.tab.Panel2.png TabPanel component}
124350 Example usage:
124351
124352     Ext.create('Ext.tab.Panel', {
124353         width: 300,
124354         height: 200,
124355         activeTab: 0,
124356         bodyPadding: 10,        
124357         tabPosition: 'bottom',
124358         items: [
124359             {
124360                 title: 'Tab 1',
124361                 html : 'A simple tab'
124362             },
124363             {
124364                 title: 'Tab 2',
124365                 html : 'Another one'
124366             }
124367         ],
124368         renderTo : Ext.getBody()
124369     }); 
124370
124371 The {@link #setActiveTab} is a very useful method in TabPanel which will allow you to change the current active tab. You can either give it an index or 
124372 an instance of a tab.
124373
124374 Example usage:
124375
124376     var tabs = Ext.create('Ext.tab.Panel', {
124377         items: [
124378             {
124379                 id   : 'my-tab',
124380                 title: 'Tab 1',
124381                 html : 'A simple tab'
124382             },
124383             {
124384                 title: 'Tab 2',
124385                 html : 'Another one'
124386             }
124387         ],
124388         renderTo : Ext.getBody()
124389     });
124390     
124391     var tab = Ext.getCmp('my-tab');
124392     
124393     Ext.create('Ext.button.Button', {
124394         renderTo: Ext.getBody(),
124395         text    : 'Select the first tab',
124396         scope   : this,
124397         handler : function() {
124398             tabs.setActiveTab(tab);
124399         }
124400     });
124401     
124402     Ext.create('Ext.button.Button', {
124403         text    : 'Select the second tab',
124404         scope   : this,
124405         handler : function() {
124406             tabs.setActiveTab(1);
124407         },
124408         renderTo : Ext.getBody()        
124409     });
124410
124411 The {@link #getActiveTab} is a another useful method in TabPanel which will return the current active tab.
124412
124413 Example usage:
124414
124415     var tabs = Ext.create('Ext.tab.Panel', {
124416         items: [
124417             {
124418                 title: 'Tab 1',
124419                 html : 'A simple tab'
124420             },
124421             {
124422                 title: 'Tab 2',
124423                 html : 'Another one'
124424             }
124425         ],
124426         renderTo : Ext.getBody()        
124427     });
124428     
124429     Ext.create('Ext.button.Button', {
124430         text    : 'Get active tab',
124431         scope   : this,
124432         handler : function() {
124433             var tab = tabs.getActiveTab();
124434             alert('Current tab: ' + tab.title);
124435         },
124436         renderTo : Ext.getBody()        
124437     });
124438
124439 Adding a new tab is very simple with a TabPanel. You simple call the {@link #add} method with an config object for a panel.
124440
124441 Example usage:
124442
124443     var tabs = Ext.Create('Ext.tab.Panel', {
124444         items: [
124445             {
124446                 title: 'Tab 1',
124447                 html : 'A simple tab'
124448             },
124449             {
124450                 title: 'Tab 2',
124451                 html : 'Another one'
124452             }
124453         ],
124454         renderTo : Ext.getBody()        
124455     });
124456     
124457     Ext.create('Ext.button.Button', {
124458         text    : 'New tab',
124459         scope   : this,
124460         handler : function() {
124461             var tab = tabs.add({
124462                 title: 'Tab ' + (tabs.items.length + 1), //we use the tabs.items property to get the length of current items/tabs
124463                 html : 'Another one'
124464             });
124465             
124466             tabs.setActiveTab(tab);
124467         },
124468         renderTo : Ext.getBody()
124469     });
124470
124471 Additionally, removing a tab is very also simple with a TabPanel. You simple call the {@link #remove} method with an config object for a panel.
124472
124473 Example usage:
124474
124475     var tabs = Ext.Create('Ext.tab.Panel', {        
124476         items: [
124477             {
124478                 title: 'Tab 1',
124479                 html : 'A simple tab'
124480             },
124481             {
124482                 id   : 'remove-this-tab',
124483                 title: 'Tab 2',
124484                 html : 'Another one'
124485             }
124486         ],
124487         renderTo : Ext.getBody()
124488     });
124489     
124490     Ext.Create('Ext.button.Button', {
124491         text    : 'Remove tab',
124492         scope   : this,
124493         handler : function() {
124494             var tab = Ext.getCmp('remove-this-tab');
124495             tabs.remove(tab);
124496         },
124497         renderTo : Ext.getBody()
124498     });
124499
124500  * @extends Ext.Panel
124501  * @constructor
124502  * @param {Object} config The configuration options
124503  * @xtype tabpanel
124504  * @markdown
124505  */
124506 Ext.define('Ext.tab.Panel', {
124507     extend: 'Ext.panel.Panel',
124508     alias: 'widget.tabpanel',
124509     alternateClassName: ['Ext.TabPanel'],
124510
124511     requires: ['Ext.layout.container.Card', 'Ext.tab.Bar'],
124512
124513     /**
124514      * @cfg {String} tabPosition The position where the tab strip should be rendered (defaults to <code>'top'</code>).
124515      * In 4.0, The only other supported value is <code>'bottom'</code>.
124516      */
124517     tabPosition : 'top',
124518     
124519     /**
124520      * @cfg {Object} tabBar Optional configuration object for the internal {@link Ext.tab.Bar}. If present, this is 
124521      * passed straight through to the TabBar's constructor
124522      */
124523
124524     /**
124525      * @cfg {Object} layout Optional configuration object for the internal {@link Ext.layout.container.Card card layout}.
124526      * If present, this is passed straight through to the layout's constructor
124527      */
124528
124529     /**
124530      * @cfg {Boolean} removePanelHeader True to instruct each Panel added to the TabContainer to not render its header 
124531      * element. This is to ensure that the title of the panel does not appear twice. Defaults to true.
124532      */
124533     removePanelHeader: true,
124534
124535     /**
124536      * @cfg Boolean plain
124537      * True to not show the full background on the TabBar
124538      */
124539     plain: false,
124540
124541     /**
124542      * @cfg {String} itemCls The class added to each child item of this TabPanel. Defaults to 'x-tabpanel-child'.
124543      */
124544     itemCls: 'x-tabpanel-child',
124545
124546     /**
124547      * @cfg {Number} minTabWidth The minimum width for a tab in the {@link #tabBar}. Defaults to <code>30</code>.
124548      */
124549
124550     /**
124551      * @cfg {Boolean} deferredRender
124552      * <p><tt>true</tt> by default to defer the rendering of child <tt>{@link Ext.container.Container#items items}</tt>
124553      * to the browsers DOM until a tab is activated. <tt>false</tt> will render all contained
124554      * <tt>{@link Ext.container.Container#items items}</tt> as soon as the {@link Ext.layout.container.Card layout}
124555      * is rendered. If there is a significant amount of content or a lot of heavy controls being
124556      * rendered into panels that are not displayed by default, setting this to <tt>true</tt> might
124557      * improve performance.</p>
124558      * <br><p>The <tt>deferredRender</tt> property is internally passed to the layout manager for
124559      * TabPanels ({@link Ext.layout.container.Card}) as its {@link Ext.layout.container.Card#deferredRender}
124560      * configuration value.</p>
124561      * <br><p><b>Note</b>: leaving <tt>deferredRender</tt> as <tt>true</tt> means that the content
124562      * within an unactivated tab will not be available</p>
124563      */
124564     deferredRender : true,
124565
124566     //inherit docs
124567     initComponent: function() {
124568         var me = this,
124569             dockedItems = me.dockedItems || [],
124570             activeTab = me.activeTab || 0;
124571
124572         me.layout = Ext.create('Ext.layout.container.Card', Ext.apply({
124573             owner: me,
124574             deferredRender: me.deferredRender,
124575             itemCls: me.itemCls
124576         }, me.layout));
124577
124578         /**
124579          * @property tabBar
124580          * @type Ext.TabBar
124581          * Internal reference to the docked TabBar
124582          */
124583         me.tabBar = Ext.create('Ext.tab.Bar', Ext.apply({}, me.tabBar, {
124584             dock: me.tabPosition,
124585             plain: me.plain,
124586             border: me.border,
124587             cardLayout: me.layout,
124588             tabPanel: me
124589         }));
124590
124591         if (dockedItems && !Ext.isArray(dockedItems)) {
124592             dockedItems = [dockedItems];
124593         }
124594
124595         dockedItems.push(me.tabBar);
124596         me.dockedItems = dockedItems;
124597
124598         me.addEvents(
124599             /**
124600              * @event beforetabchange
124601              * Fires before a tab change (activated by {@link #setActiveTab}). Return false in any listener to cancel
124602              * the tabchange
124603              * @param {Ext.tab.Panel} tabPanel The TabPanel
124604              * @param {Ext.Component} newCard The card that is about to be activated
124605              * @param {Ext.Component} oldCard The card that is currently active
124606              */
124607             'beforetabchange',
124608
124609             /**
124610              * @event tabchange
124611              * Fires when a new tab has been activated (activated by {@link #setActiveTab}).
124612              * @param {Ext.tab.Panel} tabPanel The TabPanel
124613              * @param {Ext.Component} newCard The newly activated item
124614              * @param {Ext.Component} oldCard The previously active item
124615              */
124616             'tabchange'
124617         );
124618         me.callParent(arguments);
124619
124620         //set the active tab
124621         me.setActiveTab(activeTab);
124622         //set the active tab after initial layout
124623         me.on('afterlayout', me.afterInitialLayout, me, {single: true});
124624     },
124625
124626     /**
124627      * @private
124628      * We have to wait until after the initial layout to visually activate the activeTab (if set).
124629      * The active tab has different margins than normal tabs, so if the initial layout happens with
124630      * a tab active, its layout will be offset improperly due to the active margin style. Waiting
124631      * until after the initial layout avoids this issue.
124632      */
124633     afterInitialLayout: function() {
124634         var me = this,
124635             card = me.getComponent(me.activeTab);
124636             
124637         if (card) {
124638             me.layout.setActiveItem(card);
124639         }
124640     },
124641
124642     /**
124643      * Makes the given card active (makes it the visible card in the TabPanel's CardLayout and highlights the Tab)
124644      * @param {Ext.Component} card The card to make active
124645      */
124646     setActiveTab: function(card) {
124647         var me = this,
124648             previous;
124649
124650         card = me.getComponent(card);
124651         if (card) {
124652             previous = me.getActiveTab();
124653             
124654             if (previous && previous !== card && me.fireEvent('beforetabchange', me, card, previous) === false) {
124655                 return false;
124656             }
124657             
124658             me.tabBar.setActiveTab(card.tab);
124659             me.activeTab = card;
124660             if (me.rendered) {
124661                 me.layout.setActiveItem(card);
124662             }
124663             
124664             if (previous && previous !== card) {
124665                 me.fireEvent('tabchange', me, card, previous);
124666             }
124667         }
124668     },
124669
124670     /**
124671      * Returns the item that is currently active inside this TabPanel. Note that before the TabPanel first activates a
124672      * child component this will return whatever was configured in the {@link #activeTab} config option 
124673      * @return {Ext.Component/Integer} The currently active item
124674      */
124675     getActiveTab: function() {
124676         return this.activeTab;
124677     },
124678
124679     /**
124680      * Returns the {@link Ext.tab.Bar} currently used in this TabPanel
124681      * @return {Ext.TabBar} The TabBar
124682      */
124683     getTabBar: function() {
124684         return this.tabBar;
124685     },
124686
124687     /**
124688      * @ignore
124689      * Makes sure we have a Tab for each item added to the TabPanel
124690      */
124691     onAdd: function(item, index) {
124692         var me = this;
124693
124694         item.tab = me.tabBar.insert(index, {
124695             xtype: 'tab',
124696             card: item,
124697             disabled: item.disabled,
124698             closable: item.closable,
124699             hidden: item.hidden,
124700             tabBar: me.tabBar
124701         });
124702         
124703         item.on({
124704             scope : me,
124705             enable: me.onItemEnable,
124706             disable: me.onItemDisable,
124707             beforeshow: me.onItemBeforeShow,
124708             iconchange: me.onItemIconChange,
124709             titlechange: me.onItemTitleChange
124710         });
124711
124712         if (item.isPanel) {
124713             if (me.removePanelHeader) {
124714                 item.preventHeader = true;
124715                 if (item.rendered) {
124716                     item.updateHeader();
124717                 }
124718             }
124719             if (item.isPanel && me.border) {
124720                 item.setBorder(false);
124721             }
124722         }
124723
124724         // ensure that there is at least one active tab
124725         if (this.rendered && me.items.getCount() === 1) {
124726             me.setActiveTab(0);
124727         }
124728     },
124729     
124730     /**
124731      * @private
124732      * Enable corresponding tab when item is enabled.
124733      */
124734     onItemEnable: function(item){
124735         item.tab.enable();
124736     },
124737
124738     /**
124739      * @private
124740      * Disable corresponding tab when item is enabled.
124741      */    
124742     onItemDisable: function(item){
124743         item.tab.disable();
124744     },
124745     
124746     /**
124747      * @private
124748      * Sets activeTab before item is shown.
124749      */
124750     onItemBeforeShow: function(item) {
124751         if (item !== this.activeTab) {
124752             this.setActiveTab(item);
124753             return false;
124754         }    
124755     },
124756     
124757     /**
124758      * @private
124759      * Update the tab iconCls when panel iconCls has been set or changed.
124760      */
124761     onItemIconChange: function(item, newIconCls) {
124762         item.tab.setIconCls(newIconCls);
124763         this.getTabBar().doLayout();
124764     },
124765     
124766     /**
124767      * @private
124768      * Update the tab title when panel title has been set or changed.
124769      */
124770     onItemTitleChange: function(item, newTitle) {
124771         item.tab.setText(newTitle);
124772         this.getTabBar().doLayout();
124773     },
124774
124775
124776     /**
124777      * @ignore
124778      * If we're removing the currently active tab, activate the nearest one. The item is removed when we call super,
124779      * so we can do preprocessing before then to find the card's index
124780      */
124781     doRemove: function(item, autoDestroy) {
124782         var me = this,
124783             items = me.items,
124784             /**
124785              * At this point the item hasn't been removed from the items collection.
124786              * As such, if we want to check if there are no more tabs left, we have to
124787              * check for one, as opposed to 0.
124788              */
124789             hasItemsLeft = items.getCount() > 1;
124790
124791         if (me.destroying || !hasItemsLeft) {
124792             me.activeTab = null;
124793         } else if (item === me.activeTab) {
124794              me.setActiveTab(item.next() || items.getAt(0)); 
124795         }
124796         me.callParent(arguments);
124797
124798         // Remove the two references
124799         delete item.tab.card;
124800         delete item.tab;
124801     },
124802
124803     /**
124804      * @ignore
124805      * Makes sure we remove the corresponding Tab when an item is removed
124806      */
124807     onRemove: function(item, autoDestroy) {
124808         var me = this;
124809         
124810         item.un({
124811             scope : me,
124812             enable: me.onItemEnable,
124813             disable: me.onItemDisable,
124814             beforeshow: me.onItemBeforeShow
124815         });
124816         if (!me.destroying && item.tab.ownerCt == me.tabBar) {
124817             me.tabBar.remove(item.tab);
124818         }
124819     }
124820 });
124821
124822 /**
124823  * @class Ext.toolbar.Spacer
124824  * @extends Ext.toolbar.Item
124825  * A simple element that adds extra horizontal space between items in a toolbar.
124826  * By default a 2px wide space is added via css specification:
124827  *
124828  *     .x-toolbar .x-toolbar-spacer {
124829  *         width:2px;
124830  *     }
124831  *
124832  * ## Example
124833  *
124834  * {@img Ext.toolbar.Spacer/Ext.toolbar.Spacer.png Toolbar Spacer}
124835  *
124836  *     Ext.create('Ext.panel.Panel', {
124837  *         title: 'Toolbar Spacer Example',
124838  *         width: 300,
124839  *         height: 200,
124840  *         tbar : [
124841  *             'Item 1',
124842  *             {xtype: 'tbspacer'}, // or ' '
124843  *             'Item 2',
124844  *             // space width is also configurable via javascript
124845  *             {xtype: 'tbspacer', width: 50}, // add a 50px space
124846  *             'Item 3'
124847  *         ],
124848  *         renderTo: Ext.getBody()
124849  *     });   
124850  *
124851  * @constructor
124852  * Creates a new Spacer
124853  * @xtype tbspacer
124854  */
124855 Ext.define('Ext.toolbar.Spacer', {
124856     extend: 'Ext.Component',
124857     alias: 'widget.tbspacer',
124858     alternateClassName: 'Ext.Toolbar.Spacer',
124859     baseCls: Ext.baseCSSPrefix + 'toolbar-spacer',
124860     focusable: false
124861 });
124862 /**
124863  * @class Ext.tree.Column
124864  * @extends Ext.grid.column.Column
124865  * 
124866  * Provides indentation and folder structure markup for a Tree taking into account
124867  * depth and position within the tree hierarchy.
124868  * 
124869  * @private
124870  */
124871 Ext.define('Ext.tree.Column', {
124872     extend: 'Ext.grid.column.Column',
124873     alias: 'widget.treecolumn',
124874
124875     initComponent: function() {
124876         var origRenderer = this.renderer || this.defaultRenderer,
124877             origScope    = this.scope || window;
124878
124879         this.renderer = function(value, metaData, record, rowIdx, colIdx, store, view) {
124880             var buf   = [],
124881                 format = Ext.String.format,
124882                 depth = record.getDepth(),
124883                 treePrefix  = Ext.baseCSSPrefix + 'tree-',
124884                 elbowPrefix = treePrefix + 'elbow-',
124885                 expanderCls = treePrefix + 'expander',
124886                 imgText     = '<img src="{1}" class="{0}" />',
124887                 checkboxText= '<input type="button" role="checkbox" class="{0}" {1} />',
124888                 formattedValue = origRenderer.apply(origScope, arguments),
124889                 href = record.get('href'),
124890                 target = record.get('hrefTarget'),
124891                 cls = record.get('cls');
124892
124893             while (record) {
124894                 if (!record.isRoot() || (record.isRoot() && view.rootVisible)) {
124895                     if (record.getDepth() === depth) {
124896                         buf.unshift(format(imgText,
124897                             treePrefix + 'icon ' + 
124898                             treePrefix + 'icon' + (record.get('icon') ? '-inline ' : (record.isLeaf() ? '-leaf ' : '-parent ')) +
124899                             (record.get('iconCls') || ''),
124900                             record.get('icon') || Ext.BLANK_IMAGE_URL
124901                         ));
124902                         if (record.get('checked') !== null) {
124903                             buf.unshift(format(
124904                                 checkboxText,
124905                                 (treePrefix + 'checkbox') + (record.get('checked') ? ' ' + treePrefix + 'checkbox-checked' : ''),
124906                                 record.get('checked') ? 'aria-checked="true"' : ''
124907                             ));
124908                             if (record.get('checked')) {
124909                                 metaData.tdCls += (' ' + Ext.baseCSSPrefix + 'tree-checked');
124910                             }
124911                         }
124912                         if (record.isLast()) {
124913                             if (record.isLeaf() || (record.isLoaded() && !record.hasChildNodes())) {
124914                                 buf.unshift(format(imgText, (elbowPrefix + 'end'), Ext.BLANK_IMAGE_URL));
124915                             } else {
124916                                 buf.unshift(format(imgText, (elbowPrefix + 'end-plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
124917                             }
124918                             
124919                         } else {
124920                             if (record.isLeaf() || (record.isLoaded() && !record.hasChildNodes())) {
124921                                 buf.unshift(format(imgText, (treePrefix + 'elbow'), Ext.BLANK_IMAGE_URL));
124922                             } else {
124923                                 buf.unshift(format(imgText, (elbowPrefix + 'plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
124924                             }
124925                         }
124926                     } else {
124927                         if (record.isLast() || record.getDepth() === 0) {
124928                             buf.unshift(format(imgText, (elbowPrefix + 'empty'), Ext.BLANK_IMAGE_URL));
124929                         } else if (record.getDepth() !== 0) {
124930                             buf.unshift(format(imgText, (elbowPrefix + 'line'), Ext.BLANK_IMAGE_URL));
124931                         }                      
124932                     }
124933                 }
124934                 record = record.parentNode;
124935             }
124936             if (href) {
124937                 formattedValue = format('<a href="{0}" target="{1}">{2}</a>', href, target, formattedValue);
124938             }
124939             if (cls) {
124940                 metaData.tdCls += ' ' + cls;
124941             }
124942             return buf.join("") + formattedValue;
124943         };
124944         this.callParent(arguments);
124945     },
124946
124947     defaultRenderer: function(value) {
124948         return value;
124949     }
124950 });
124951 /**
124952  * @class Ext.tree.View
124953  * @extends Ext.view.Table
124954  */
124955 Ext.define('Ext.tree.View', {
124956     extend: 'Ext.view.Table',
124957     alias: 'widget.treeview',
124958
124959     loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
124960     expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
124961
124962     expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
124963     checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
124964     expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
124965
124966     blockRefresh: true,
124967
124968     /** 
124969      * @cfg {Boolean} rootVisible <tt>false</tt> to hide the root node (defaults to <tt>true</tt>)
124970      */
124971     rootVisible: true,
124972
124973     /** 
124974      * @cfg {Boolean} animate <tt>true</tt> to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
124975      */
124976
124977     expandDuration: 250,
124978     collapseDuration: 250,
124979     
124980     toggleOnDblClick: true,
124981
124982     initComponent: function() {
124983         var me = this;
124984         
124985         if (me.initialConfig.animate === undefined) {
124986             me.animate = Ext.enableFx;
124987         }
124988         
124989         me.store = Ext.create('Ext.data.NodeStore', {
124990             recursive: true,
124991             rootVisible: me.rootVisible,
124992             listeners: {
124993                 beforeexpand: me.onBeforeExpand,
124994                 expand: me.onExpand,
124995                 beforecollapse: me.onBeforeCollapse,
124996                 collapse: me.onCollapse,
124997                 scope: me
124998             }
124999         });
125000         
125001         if (me.node) {
125002             me.setRootNode(me.node);
125003         }
125004         me.animQueue = {};
125005         me.callParent(arguments);
125006     },
125007     
125008     onClear: function(){
125009         this.store.removeAll();    
125010     },
125011
125012     setRootNode: function(node) {
125013         var me = this;        
125014         me.store.setNode(node);
125015         me.node = node;
125016         if (!me.rootVisible) {
125017             node.expand();
125018         }
125019     },
125020     
125021     onRender: function() {
125022         var me = this,
125023             opts = {delegate: me.expanderSelector},
125024             el;
125025
125026         me.callParent(arguments);
125027
125028         el = me.el;
125029         el.on({
125030             scope: me,
125031             delegate: me.expanderSelector,
125032             mouseover: me.onExpanderMouseOver,
125033             mouseout: me.onExpanderMouseOut
125034         });
125035         el.on({
125036             scope: me,
125037             delegate: me.checkboxSelector,
125038             click: me.onCheckboxChange
125039         });
125040     },
125041
125042     onCheckboxChange: function(e, t) {
125043         var item = e.getTarget(this.getItemSelector(), this.getTargetEl()),
125044             record, value;
125045             
125046         if (item) {
125047             record = this.getRecord(item);
125048             value = !record.get('checked');
125049             record.set('checked', value);
125050             this.fireEvent('checkchange', record, value);
125051         }
125052     },
125053
125054     getChecked: function() {
125055         var checked = [];
125056         this.node.cascadeBy(function(rec){
125057             if (rec.get('checked')) {
125058                 checked.push(rec);
125059             }
125060         });
125061         return checked;
125062     },
125063     
125064     isItemChecked: function(rec){
125065         return rec.get('checked');
125066     },
125067
125068     createAnimWrap: function(record, index) {
125069         var thHtml = '',
125070             headerCt = this.panel.headerCt,
125071             headers = headerCt.getGridColumns(),
125072             i = 0, len = headers.length, item,
125073             node = this.getNode(record),
125074             tmpEl, nodeEl;
125075
125076         for (; i < len; i++) {
125077             item = headers[i];
125078             thHtml += '<th style="width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;"></th>';
125079         }
125080
125081         nodeEl = Ext.get(node);        
125082         tmpEl = nodeEl.insertSibling({
125083             tag: 'tr',
125084             html: [
125085                 '<td colspan="' + headerCt.getColumnCount() + '">',
125086                     '<div class="' + Ext.baseCSSPrefix + 'tree-animator-wrap' + '">',
125087                         '<table class="' + Ext.baseCSSPrefix + 'grid-table" style="width: ' + headerCt.getFullWidth() + 'px;"><tbody>',
125088                             thHtml,
125089                         '</tbody></table>',
125090                     '</div>',
125091                 '</td>'
125092             ].join('')
125093         }, 'after');
125094
125095         return {
125096             record: record,
125097             node: node,
125098             el: tmpEl,
125099             expanding: false,
125100             collapsing: false,
125101             animating: false,
125102             animateEl: tmpEl.down('div'),
125103             targetEl: tmpEl.down('tbody')
125104         };
125105     },
125106
125107     getAnimWrap: function(parent) {
125108         if (!this.animate) {
125109             return null;
125110         }
125111
125112         // We are checking to see which parent is having the animation wrap
125113         while (parent) {
125114             if (parent.animWrap) {
125115                 return parent.animWrap;
125116             }
125117             parent = parent.parentNode;
125118         }
125119         return null;
125120     },
125121
125122     doAdd: function(nodes, records, index) {
125123         // If we are adding records which have a parent that is currently expanding
125124         // lets add them to the animation wrap
125125         var me = this,
125126             record = records[0],
125127             parent = record.parentNode,
125128             a = me.all.elements,
125129             relativeIndex = 0,
125130             animWrap = me.getAnimWrap(parent),
125131             targetEl, children, len;
125132
125133         if (!animWrap || !animWrap.expanding) {
125134             me.resetScrollers();
125135             return me.callParent(arguments);
125136         }
125137
125138         // We need the parent that has the animWrap, not the nodes parent
125139         parent = animWrap.record;
125140         
125141         // If there is an anim wrap we do our special magic logic
125142         targetEl = animWrap.targetEl;
125143         children = targetEl.dom.childNodes;
125144         
125145         // We subtract 1 from the childrens length because we have a tr in there with the th'es
125146         len = children.length - 1;
125147         
125148         // The relative index is the index in the full flat collection minus the index of the wraps parent
125149         relativeIndex = index - me.indexOf(parent) - 1;
125150         
125151         // If we are adding records to the wrap that have a higher relative index then there are currently children
125152         // it means we have to append the nodes to the wrap
125153         if (!len || relativeIndex >= len) {
125154             targetEl.appendChild(nodes);
125155         }
125156         // If there are already more children then the relative index it means we are adding child nodes of
125157         // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
125158         else {
125159             // +1 because of the tr with th'es that is already there
125160             Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
125161         }
125162         
125163         // We also have to update the CompositeElementLite collection of the DataView
125164         if (index < a.length) {
125165             a.splice.apply(a, [index, 0].concat(nodes));
125166         }
125167         else {            
125168             a.push.apply(a, nodes);
125169         }
125170         
125171         // If we were in an animation we need to now change the animation
125172         // because the targetEl just got higher.
125173         if (animWrap.isAnimating) {
125174             me.onExpand(parent);
125175         }
125176     },
125177     
125178     doRemove: function(record, index) {
125179         // If we are adding records which have a parent that is currently expanding
125180         // lets add them to the animation wrap
125181         var me = this,
125182             parent = record.parentNode,
125183             all = me.all,
125184             animWrap = me.getAnimWrap(record),
125185             node = all.item(index).dom;
125186
125187         if (!animWrap || !animWrap.collapsing) {
125188             me.resetScrollers();
125189             return me.callParent(arguments);
125190         }
125191
125192         animWrap.targetEl.appendChild(node);
125193         all.removeElement(index);
125194     },
125195
125196     onBeforeExpand: function(parent, records, index) {
125197         var me = this,
125198             animWrap;
125199             
125200         if (!me.rendered || !me.animate) {
125201             return;
125202         }
125203
125204         if (me.getNode(parent)) {
125205             animWrap = me.getAnimWrap(parent);
125206             if (!animWrap) {
125207                 animWrap = parent.animWrap = me.createAnimWrap(parent);
125208                 animWrap.animateEl.setHeight(0);
125209             }
125210             else if (animWrap.collapsing) {
125211                 // If we expand this node while it is still expanding then we
125212                 // have to remove the nodes from the animWrap.
125213                 animWrap.targetEl.select(me.itemSelector).remove();
125214             } 
125215             animWrap.expanding = true;
125216             animWrap.collapsing = false;
125217         }
125218     },
125219
125220     onExpand: function(parent) {
125221         var me = this,
125222             queue = me.animQueue,
125223             id = parent.getId(),
125224             animWrap,
125225             animateEl, 
125226             targetEl,
125227             queueItem;        
125228         
125229         if (me.singleExpand) {
125230             me.ensureSingleExpand(parent);
125231         }
125232         
125233         animWrap = me.getAnimWrap(parent);
125234
125235         if (!animWrap) {
125236             me.resetScrollers();
125237             return;
125238         }
125239         
125240         animateEl = animWrap.animateEl;
125241         targetEl = animWrap.targetEl;
125242
125243         animateEl.stopAnimation();
125244         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
125245         queue[id] = true;
125246         animateEl.slideIn('t', {
125247             duration: me.expandDuration,
125248             listeners: {
125249                 scope: me,
125250                 lastframe: function() {
125251                     // Move all the nodes out of the anim wrap to their proper location
125252                     animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
125253                     animWrap.el.remove();
125254                     me.resetScrollers();
125255                     delete animWrap.record.animWrap;
125256                     delete queue[id];
125257                 }
125258             }
125259         });
125260         
125261         animWrap.isAnimating = true;
125262     },
125263     
125264     resetScrollers: function(){
125265         var panel = this.panel;
125266         
125267         panel.determineScrollbars();
125268         panel.invalidateScroller();
125269     },
125270
125271     onBeforeCollapse: function(parent, records, index) {
125272         var me = this,
125273             animWrap;
125274             
125275         if (!me.rendered || !me.animate) {
125276             return;
125277         }
125278
125279         if (me.getNode(parent)) {
125280             animWrap = me.getAnimWrap(parent);
125281             if (!animWrap) {
125282                 animWrap = parent.animWrap = me.createAnimWrap(parent, index);
125283             }
125284             else if (animWrap.expanding) {
125285                 // If we collapse this node while it is still expanding then we
125286                 // have to remove the nodes from the animWrap.
125287                 animWrap.targetEl.select(this.itemSelector).remove();
125288             }
125289             animWrap.expanding = false;
125290             animWrap.collapsing = true;
125291         }
125292     },
125293     
125294     onCollapse: function(parent) {
125295         var me = this,
125296             queue = me.animQueue,
125297             id = parent.getId(),
125298             animWrap = me.getAnimWrap(parent),
125299             animateEl, targetEl;
125300
125301         if (!animWrap) {
125302             me.resetScrollers();
125303             return;
125304         }
125305         
125306         animateEl = animWrap.animateEl;
125307         targetEl = animWrap.targetEl;
125308
125309         queue[id] = true;
125310         
125311         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
125312         animateEl.stopAnimation();
125313         animateEl.slideOut('t', {
125314             duration: me.collapseDuration,
125315             listeners: {
125316                 scope: me,
125317                 lastframe: function() {
125318                     animWrap.el.remove();
125319                     delete animWrap.record.animWrap;
125320                     me.resetScrollers();
125321                     delete queue[id];
125322                 }             
125323             }
125324         });
125325         animWrap.isAnimating = true;
125326     },
125327     
125328     /**
125329      * Checks if a node is currently undergoing animation
125330      * @private
125331      * @param {Ext.data.Model} node The node
125332      * @return {Boolean} True if the node is animating
125333      */
125334     isAnimating: function(node) {
125335         return !!this.animQueue[node.getId()];    
125336     },
125337     
125338     collectData: function(records) {
125339         var data = this.callParent(arguments),
125340             rows = data.rows,
125341             len = rows.length,
125342             i = 0,
125343             row, record;
125344             
125345         for (; i < len; i++) {
125346             row = rows[i];
125347             record = records[i];
125348             if (record.get('qtip')) {
125349                 row.rowAttr = 'data-qtip="' + record.get('qtip') + '"';
125350                 if (record.get('qtitle')) {
125351                     row.rowAttr += ' ' + 'data-qtitle="' + record.get('qtitle') + '"';
125352                 }
125353             }
125354             if (record.isExpanded()) {
125355                 row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
125356             }
125357             if (record.isLoading()) {
125358                 row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
125359             }
125360         }
125361         
125362         return data;
125363     },
125364     
125365     /**
125366      * Expand a record that is loaded in the view.
125367      * @param {Ext.data.Model} record The record to expand
125368      * @param {Boolean} deep (optional) True to expand nodes all the way down the tree hierarchy.
125369      * @param {Function} callback (optional) The function to run after the expand is completed
125370      * @param {Object} scope (optional) The scope of the callback function.
125371      */
125372     expand: function(record, deep, callback, scope) {
125373         return record.expand(deep, callback, scope);
125374     },
125375     
125376     /**
125377      * Collapse a record that is loaded in the view.
125378      * @param {Ext.data.Model} record The record to collapse
125379      * @param {Boolean} deep (optional) True to collapse nodes all the way up the tree hierarchy.
125380      * @param {Function} callback (optional) The function to run after the collapse is completed
125381      * @param {Object} scope (optional) The scope of the callback function.
125382      */
125383     collapse: function(record, deep, callback, scope) {
125384         return record.collapse(deep, callback, scope);
125385     },
125386     
125387     /**
125388      * Toggle a record between expanded and collapsed.
125389      * @param {Ext.data.Record} recordInstance
125390      */
125391     toggle: function(record) {
125392         this[record.isExpanded() ? 'collapse' : 'expand'](record);
125393     },
125394     
125395     onItemDblClick: function(record, item, index) {
125396         this.callParent(arguments);
125397         if (this.toggleOnDblClick) {
125398             this.toggle(record);
125399         }
125400     },
125401     
125402     onBeforeItemMouseDown: function(record, item, index, e) {
125403         if (e.getTarget(this.expanderSelector, item)) {
125404             return false;
125405         }
125406         return this.callParent(arguments);
125407     },
125408     
125409     onItemClick: function(record, item, index, e) {
125410         if (e.getTarget(this.expanderSelector, item)) {
125411             this.toggle(record);
125412             return false;
125413         }
125414         return this.callParent(arguments);
125415     },
125416     
125417     onExpanderMouseOver: function(e, t) {
125418         e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
125419     },
125420     
125421     onExpanderMouseOut: function(e, t) {
125422         e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
125423     },
125424     
125425     /**
125426      * Gets the base TreeStore from the bound TreePanel.
125427      */
125428     getTreeStore: function() {
125429         return this.panel.store;
125430     },    
125431     
125432     ensureSingleExpand: function(node) {
125433         var parent = node.parentNode;
125434         if (parent) {
125435             parent.eachChild(function(child) {
125436                 if (child !== node && child.isExpanded()) {
125437                     child.collapse();
125438                 }
125439             });
125440         }
125441     }
125442 });
125443 /**
125444  * @class Ext.tree.Panel
125445  * @extends Ext.panel.Table
125446  * 
125447  * The TreePanel provides tree-structured UI representation of tree-structured data.
125448  * A TreePanel must be bound to a {@link Ext.data.TreeStore}. TreePanel's support
125449  * multiple columns through the {@link columns} configuration. 
125450  * 
125451  * Simple TreePanel using inline data.
125452  *
125453  * {@img Ext.tree.Panel/Ext.tree.Panel1.png Ext.tree.Panel component}
125454  * 
125455  * ## Simple Tree Panel (no columns)
125456  *
125457  *     var store = Ext.create('Ext.data.TreeStore', {
125458  *         root: {
125459  *             expanded: true, 
125460  *             text:"",
125461  *             user:"",
125462  *             status:"", 
125463  *             children: [
125464  *                 { text:"detention", leaf: true },
125465  *                 { text:"homework", expanded: true, 
125466  *                     children: [
125467  *                         { text:"book report", leaf: true },
125468  *                         { text:"alegrbra", leaf: true}
125469  *                     ]
125470  *                 },
125471  *                 { text: "buy lottery tickets", leaf:true }
125472  *             ]
125473  *         }
125474  *     });     
125475  *             
125476  *     Ext.create('Ext.tree.Panel', {
125477  *         title: 'Simple Tree',
125478  *         width: 200,
125479  *         height: 150,
125480  *         store: store,
125481  *         rootVisible: false,        
125482  *         renderTo: Ext.getBody()
125483  *     });
125484  *
125485  * @xtype treepanel
125486  */
125487 Ext.define('Ext.tree.Panel', {
125488     extend: 'Ext.panel.Table',
125489     alias: 'widget.treepanel',
125490     alternateClassName: ['Ext.tree.TreePanel', 'Ext.TreePanel'],
125491     requires: ['Ext.tree.View', 'Ext.selection.TreeModel', 'Ext.tree.Column'],
125492     viewType: 'treeview',
125493     selType: 'treemodel',
125494     
125495     treeCls: Ext.baseCSSPrefix + 'tree-panel',
125496     
125497     /**
125498      * @cfg {Boolean} lines false to disable tree lines (defaults to true)
125499      */
125500     lines: true,
125501     
125502     /**
125503      * @cfg {Boolean} useArrows true to use Vista-style arrows in the tree (defaults to false)
125504      */
125505     useArrows: false,
125506     
125507     /**
125508      * @cfg {Boolean} singleExpand <tt>true</tt> if only 1 node per branch may be expanded
125509      */
125510     singleExpand: false,
125511     
125512     ddConfig: {
125513         enableDrag: true,
125514         enableDrop: true
125515     },
125516     
125517     /** 
125518      * @cfg {Boolean} animate <tt>true</tt> to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
125519      */
125520             
125521     /** 
125522      * @cfg {Boolean} rootVisible <tt>false</tt> to hide the root node (defaults to <tt>true</tt>)
125523      */
125524     rootVisible: true,
125525     
125526     /** 
125527      * @cfg {Boolean} displayField The field inside the model that will be used as the node's text. (defaults to <tt>text</tt>)
125528      */    
125529     displayField: 'text',
125530
125531     /** 
125532      * @cfg {Boolean} root Allows you to not specify a store on this TreePanel. This is useful for creating a simple
125533      * tree with preloaded data without having to specify a TreeStore and Model. A store and model will be created and
125534      * root will be passed to that store.
125535      */
125536     root: null,
125537     
125538     // Required for the Lockable Mixin. These are the configurations which will be copied to the
125539     // normal and locked sub tablepanels
125540     normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'],
125541     lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'],
125542
125543     /**
125544      * @cfg {Boolean} hideHeaders
125545      * Specify as <code>true</code> to hide the headers.
125546      */
125547     
125548     /**
125549      * @cfg {Boolean} folderSort Set to true to automatically prepend a leaf sorter to the store (defaults to <tt>undefined</tt>)
125550      */ 
125551     
125552     constructor: function(config) {
125553         config = config || {};
125554         if (config.animate === undefined) {
125555             config.animate = Ext.enableFx;
125556         }
125557         this.enableAnimations = config.animate;
125558         delete config.animate;
125559         
125560         this.callParent([config]);
125561     },
125562     
125563     initComponent: function() {
125564         var me = this,
125565             cls = [me.treeCls];
125566
125567         if (me.useArrows) {
125568             cls.push(Ext.baseCSSPrefix + 'tree-arrows');
125569             me.lines = false;
125570         }
125571         
125572         if (me.lines) {
125573             cls.push(Ext.baseCSSPrefix + 'tree-lines');
125574         } else if (!me.useArrows) {
125575             cls.push(Ext.baseCSSPrefix + 'tree-no-lines');
125576         }
125577         
125578         if (Ext.isString(me.store)) {
125579             me.store = Ext.StoreMgr.lookup(me.store);
125580         } else if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
125581             me.store = Ext.create('Ext.data.TreeStore', Ext.apply({}, me.store || {}, {
125582                 root: me.root,
125583                 fields: me.fields,
125584                 model: me.model,
125585                 folderSort: me.folderSort
125586             }));
125587         } else if (me.root) {
125588             me.store = Ext.data.StoreManager.lookup(me.store);
125589             me.store.setRootNode(me.root);
125590             if (me.folderSort !== undefined) {
125591                 me.store.folderSort = me.folderSort;
125592                 me.store.sort();
125593             }            
125594         }
125595         
125596         // I'm not sure if we want to this. It might be confusing
125597         // if (me.initialConfig.rootVisible === undefined && !me.getRootNode()) {
125598         //     me.rootVisible = false;
125599         // }
125600         
125601         me.viewConfig = Ext.applyIf(me.viewConfig || {}, {
125602             rootVisible: me.rootVisible,
125603             animate: me.enableAnimations,
125604             singleExpand: me.singleExpand,
125605             node: me.store.getRootNode(),
125606             hideHeaders: me.hideHeaders
125607         });
125608         
125609         me.mon(me.store, {
125610             scope: me,
125611             rootchange: me.onRootChange,
125612             clear: me.onClear
125613         });
125614     
125615         me.relayEvents(me.store, [
125616             /**
125617              * @event beforeload
125618              * Event description
125619              * @param {Ext.data.Store} store This Store
125620              * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to load the Store
125621              */
125622             'beforeload',
125623
125624             /**
125625              * @event load
125626              * Fires whenever the store reads data from a remote data source.
125627              * @param {Ext.data.store} this
125628              * @param {Array} records An array of records
125629              * @param {Boolean} successful True if the operation was successful.
125630              */
125631             'load'   
125632         ]);
125633         
125634         me.store.on({
125635             /**
125636              * @event itemappend
125637              * Fires when a new child node is appended to a node in the tree.
125638              * @param {Tree} tree The owner tree
125639              * @param {Node} parent The parent node
125640              * @param {Node} node The newly appended node
125641              * @param {Number} index The index of the newly appended node
125642              */
125643             append: me.createRelayer('itemappend'),
125644             
125645             /**
125646              * @event itemremove
125647              * Fires when a child node is removed from a node in the tree
125648              * @param {Tree} tree The owner tree
125649              * @param {Node} parent The parent node
125650              * @param {Node} node The child node removed
125651              */
125652             remove: me.createRelayer('itemremove'),
125653             
125654             /**
125655              * @event itemmove
125656              * Fires when a node is moved to a new location in the tree
125657              * @param {Tree} tree The owner tree
125658              * @param {Node} node The node moved
125659              * @param {Node} oldParent The old parent of this node
125660              * @param {Node} newParent The new parent of this node
125661              * @param {Number} index The index it was moved to
125662              */
125663             move: me.createRelayer('itemmove'),
125664             
125665             /**
125666              * @event iteminsert
125667              * Fires when a new child node is inserted in a node in tree
125668              * @param {Tree} tree The owner tree
125669              * @param {Node} parent The parent node
125670              * @param {Node} node The child node inserted
125671              * @param {Node} refNode The child node the node was inserted before
125672              */
125673             insert: me.createRelayer('iteminsert'),
125674             
125675             /**
125676              * @event beforeitemappend
125677              * Fires before a new child is appended to a node in this tree, return false to cancel the append.
125678              * @param {Tree} tree The owner tree
125679              * @param {Node} parent The parent node
125680              * @param {Node} node The child node to be appended
125681              */
125682             beforeappend: me.createRelayer('beforeitemappend'),
125683             
125684             /**
125685              * @event beforeitemremove
125686              * Fires before a child is removed from a node in this tree, return false to cancel the remove.
125687              * @param {Tree} tree The owner tree
125688              * @param {Node} parent The parent node
125689              * @param {Node} node The child node to be removed
125690              */
125691             beforeremove: me.createRelayer('beforeitemremove'),
125692             
125693             /**
125694              * @event beforeitemmove
125695              * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
125696              * @param {Tree} tree The owner tree
125697              * @param {Node} node The node being moved
125698              * @param {Node} oldParent The parent of the node
125699              * @param {Node} newParent The new parent the node is moving to
125700              * @param {Number} index The index it is being moved to
125701              */
125702             beforemove: me.createRelayer('beforeitemmove'),
125703             
125704             /**
125705              * @event beforeiteminsert
125706              * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
125707              * @param {Tree} tree The owner tree
125708              * @param {Node} parent The parent node
125709              * @param {Node} node The child node to be inserted
125710              * @param {Node} refNode The child node the node is being inserted before
125711              */
125712             beforeinsert: me.createRelayer('beforeiteminsert'),
125713              
125714             /**
125715              * @event itemexpand
125716              * Fires when a node is expanded.
125717              * @param {Node} this The expanding node
125718              */
125719             expand: me.createRelayer('itemexpand'),
125720              
125721             /**
125722              * @event itemcollapse
125723              * Fires when a node is collapsed.
125724              * @param {Node} this The collapsing node
125725              */
125726             collapse: me.createRelayer('itemcollapse'),
125727              
125728             /**
125729              * @event beforeitemexpand
125730              * Fires before a node is expanded.
125731              * @param {Node} this The expanding node
125732              */
125733             beforeexpand: me.createRelayer('beforeitemexpand'),
125734              
125735             /**
125736              * @event beforeitemcollapse
125737              * Fires before a node is collapsed.
125738              * @param {Node} this The collapsing node
125739              */
125740             beforecollapse: me.createRelayer('beforeitemcollapse')
125741         });
125742         
125743         // If the user specifies the headers collection manually then dont inject our own
125744         if (!me.columns) {
125745             if (me.initialConfig.hideHeaders === undefined) {
125746                 me.hideHeaders = true;
125747             }
125748             me.columns = [{
125749                 xtype    : 'treecolumn',
125750                 text     : 'Name',
125751                 flex     : 1,
125752                 dataIndex: me.displayField         
125753             }];
125754         }
125755         
125756         if (me.cls) {
125757             cls.push(me.cls);
125758         }
125759         me.cls = cls.join(' ');
125760         me.callParent();
125761         
125762         me.relayEvents(me.getView(), [
125763             /**
125764              * @event checkchange
125765              * Fires when a node with a checkbox's checked property changes
125766              * @param {Ext.data.Model} node The node who's checked property was changed
125767              * @param {Boolean} checked The node's new checked state
125768              */
125769             'checkchange'
125770         ]);
125771             
125772         // If the root is not visible and there is no rootnode defined, then just lets load the store
125773         if (!me.getView().rootVisible && !me.getRootNode()) {
125774             me.setRootNode({
125775                 expanded: true
125776             });
125777         }
125778     },
125779     
125780     onClear: function(){
125781         this.view.onClear();
125782     },
125783     
125784     setRootNode: function() {
125785         return this.store.setRootNode.apply(this.store, arguments);
125786     },
125787     
125788     getRootNode: function() {
125789         return this.store.getRootNode();
125790     },
125791     
125792     onRootChange: function(root) {
125793         this.view.setRootNode(root);
125794     },
125795
125796     /**
125797      * Retrieve an array of checked records.
125798      * @return {Array} An array containing the checked records
125799      */
125800     getChecked: function() {
125801         return this.getView().getChecked();
125802     },
125803     
125804     isItemChecked: function(rec) {
125805         return rec.get('checked');
125806     },
125807         
125808     /**
125809      * Expand all nodes
125810      * @param {Function} callback (optional) A function to execute when the expand finishes.
125811      * @param {Object} scope (optional) The scope of the callback function
125812      */
125813     expandAll : function(callback, scope) {
125814         var root = this.getRootNode();
125815         if (root) {
125816             root.expand(true, callback, scope);
125817         }
125818     },
125819
125820     /**
125821      * Collapse all nodes
125822      * @param {Function} callback (optional) A function to execute when the collapse finishes.
125823      * @param {Object} scope (optional) The scope of the callback function
125824      */
125825     collapseAll : function(callback, scope) {
125826         var root = this.getRootNode();
125827         if (root) {
125828             if (this.getView().rootVisible) {
125829                 root.collapse(true, callback, scope);
125830             }
125831             else {
125832                 root.collapseChildren(true, callback, scope);
125833             }
125834         }
125835     },
125836
125837     /**
125838      * Expand the tree to the path of a particular node.
125839      * @param {String} path The path to expand. The path should include a leading separator.
125840      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
125841      * @param {String} separator (optional) A separator to use. Defaults to <tt>'/'</tt>.
125842      * @param {Function} callback (optional) A function to execute when the expand finishes. The callback will be called with
125843      * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
125844      * @param {Object} scope (optional) The scope of the callback function
125845      */
125846     expandPath: function(path, field, separator, callback, scope) {
125847         var me = this,
125848             current = me.getRootNode(),
125849             index = 1,
125850             view = me.getView(),
125851             keys,
125852             expander;
125853         
125854         field = field || me.getRootNode().idProperty;
125855         separator = separator || '/';
125856         
125857         if (Ext.isEmpty(path)) {
125858             Ext.callback(callback, scope || me, [false, null]);
125859             return;
125860         }
125861         
125862         keys = path.split(separator);
125863         if (current.get(field) != keys[1]) {
125864             // invalid root
125865             Ext.callback(callback, scope || me, [false, current]);
125866             return;
125867         }
125868         
125869         expander = function(){
125870             if (++index === keys.length) {
125871                 Ext.callback(callback, scope || me, [true, current]);
125872                 return;
125873             }
125874             var node = current.findChild(field, keys[index]);
125875             if (!node) {
125876                 Ext.callback(callback, scope || me, [false, current]);
125877                 return;
125878             }
125879             current = node;
125880             current.expand(false, expander);
125881         };
125882         current.expand(false, expander);
125883     },
125884     
125885     /**
125886      * Expand the tree to the path of a particular node, then selecti t.
125887      * @param {String} path The path to select. The path should include a leading separator.
125888      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
125889      * @param {String} separator (optional) A separator to use. Defaults to <tt>'/'</tt>.
125890      * @param {Function} callback (optional) A function to execute when the select finishes. The callback will be called with
125891      * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
125892      * @param {Object} scope (optional) The scope of the callback function
125893      */
125894     selectPath: function(path, field, separator, callback, scope) {
125895         var me = this,
125896             keys,
125897             last;
125898         
125899         field = field || me.getRootNode().idProperty;
125900         separator = separator || '/';
125901         
125902         keys = path.split(separator);
125903         last = keys.pop();
125904         
125905         me.expandPath(keys.join('/'), field, separator, function(success, node){
125906             var doSuccess = false;
125907             if (success && node) {
125908                 node = node.findChild(field, last);
125909                 if (node) {
125910                     me.getSelectionModel().select(node);
125911                     Ext.callback(callback, scope || me, [true, node]);
125912                     doSuccess = true;
125913                 }
125914             } else if (node === me.getRootNode()) {
125915                 doSuccess = true;
125916             }
125917             Ext.callback(callback, scope || me, [doSuccess, node]);
125918         }, me);
125919     }
125920 });
125921 /**
125922  * @class Ext.view.DragZone
125923  * @extends Ext.dd.DragZone
125924  * @private
125925  */
125926 Ext.define('Ext.view.DragZone', {
125927     extend: 'Ext.dd.DragZone',
125928     containerScroll: false,
125929
125930     constructor: function(config) {
125931         var me = this;
125932
125933         Ext.apply(me, config);
125934
125935         // Create a ddGroup unless one has been configured.
125936         // User configuration of ddGroups allows users to specify which
125937         // DD instances can interact with each other. Using one
125938         // based on the id of the View would isolate it and mean it can only
125939         // interact with a DropZone on the same View also using a generated ID.
125940         if (!me.ddGroup) {
125941             me.ddGroup = 'view-dd-zone-' + me.view.id;
125942         }
125943
125944         // Ext.dd.DragDrop instances are keyed by the ID of their encapsulating element.
125945         // So a View's DragZone cannot use the View's main element because the DropZone must use that
125946         // because the DropZone may need to scroll on hover at a scrolling boundary, and it is the View's
125947         // main element which handles scrolling.
125948         // We use the View's parent element to drag from. Ideally, we would use the internal structure, but that 
125949         // is transient; DataView's recreate the internal structure dynamically as data changes.
125950         // TODO: Ext 5.0 DragDrop must allow multiple DD objects to share the same element.
125951         me.callParent([me.view.el.dom.parentNode]);
125952
125953         me.ddel = Ext.get(document.createElement('div'));
125954         me.ddel.addCls(Ext.baseCSSPrefix + 'grid-dd-wrap');
125955     },
125956
125957     init: function(id, sGroup, config) {
125958         this.initTarget(id, sGroup, config);
125959         this.view.mon(this.view, {
125960             itemmousedown: this.onItemMouseDown,
125961             scope: this
125962         });
125963     },
125964
125965     onItemMouseDown: function(view, record, item, index, e) {
125966         if (!this.isPreventDrag(e, record, item, index)) {
125967             this.handleMouseDown(e);
125968         }
125969     },
125970
125971     // private template method
125972     isPreventDrag: function(e) {
125973         return false;
125974     },
125975
125976     getDragData: function(e) {
125977         var view = this.view,
125978             item = e.getTarget(view.getItemSelector()),
125979             record, selectionModel, records;
125980
125981         if (item) {
125982             record = view.getRecord(item);
125983             selectionModel = view.getSelectionModel();
125984             records = selectionModel.getSelection();
125985             return {
125986                 copy: this.view.copy || (this.view.allowCopy && e.ctrlKey),
125987                 event: new Ext.EventObjectImpl(e),
125988                 view: view,
125989                 ddel: this.ddel,
125990                 item: item,
125991                 records: records,
125992                 fromPosition: Ext.fly(item).getXY()
125993             };
125994         }
125995     },
125996
125997     onInitDrag: function(x, y) {
125998         var me = this,
125999             data = me.dragData,
126000             view = data.view,
126001             selectionModel = view.getSelectionModel(),
126002             record = view.getRecord(data.item),
126003             e = data.event;
126004
126005         // Update the selection to match what would have been selected if the user had
126006         // done a full click on the target node rather than starting a drag from it
126007         if (!selectionModel.isSelected(record) || e.hasModifier()) {
126008             selectionModel.selectWithEvent(record, e);
126009         }
126010         data.records = selectionModel.getSelection();
126011
126012         me.ddel.update(me.getDragText());
126013         me.proxy.update(me.ddel.dom);
126014         me.onStartDrag(x, y);
126015         return true;
126016     },
126017
126018     getDragText: function() {
126019         var count = this.dragData.records.length;
126020         return Ext.String.format(this.dragText, count, count == 1 ? '' : 's');
126021     },
126022
126023     getRepairXY : function(e, data){
126024         return data ? data.fromPosition : false;
126025     }
126026 });
126027 Ext.define('Ext.tree.ViewDragZone', {
126028     extend: 'Ext.view.DragZone',
126029
126030     isPreventDrag: function(e, record) {
126031         return (record.get('allowDrag') === false) || !!e.getTarget(this.view.expanderSelector);
126032     },
126033     
126034     afterRepair: function() {
126035         var me = this,
126036             view = me.view,
126037             selectedRowCls = view.selectedItemCls,
126038             records = me.dragData.records,
126039             fly = Ext.fly;
126040         
126041         if (Ext.enableFx && me.repairHighlight) {
126042             // Roll through all records and highlight all the ones we attempted to drag.
126043             Ext.Array.forEach(records, function(record) {
126044                 // anonymous fns below, don't hoist up unless below is wrapped in
126045                 // a self-executing function passing in item.
126046                 var item = view.getNode(record);
126047                 
126048                 // We must remove the selected row class before animating, because
126049                 // the selected row class declares !important on its background-color.
126050                 fly(item.firstChild).highlight(me.repairHighlightColor, {
126051                     listeners: {
126052                         beforeanimate: function() {
126053                             if (view.isSelected(item)) {
126054                                 fly(item).removeCls(selectedRowCls);
126055                             }
126056                         },
126057                         afteranimate: function() {
126058                             if (view.isSelected(item)) {
126059                                 fly(item).addCls(selectedRowCls);
126060                             }
126061                         }
126062                     }
126063                 });
126064             });
126065         }
126066         me.dragging = false;
126067     }
126068 });
126069 /**
126070  * @class Ext.tree.ViewDropZone
126071  * @extends Ext.view.DropZone
126072  * @private
126073  */
126074 Ext.define('Ext.tree.ViewDropZone', {
126075     extend: 'Ext.view.DropZone',
126076
126077     /**
126078      * @cfg {Boolean} allowParentInsert
126079      * Allow inserting a dragged node between an expanded parent node and its first child that will become a
126080      * sibling of the parent when dropped (defaults to false)
126081      */
126082     allowParentInserts: false,
126083  
126084     /**
126085      * @cfg {String} allowContainerDrop
126086      * True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false)
126087      */
126088     allowContainerDrops: false,
126089
126090     /**
126091      * @cfg {String} appendOnly
126092      * True if the tree should only allow append drops (use for trees which are sorted, defaults to false)
126093      */
126094     appendOnly: false,
126095
126096     /**
126097      * @cfg {String} expandDelay
126098      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
126099      * over the target (defaults to 500)
126100      */
126101     expandDelay : 500,
126102
126103     indicatorCls: 'x-tree-ddindicator',
126104
126105     // private
126106     expandNode : function(node) {
126107         var view = this.view;
126108         if (!node.isLeaf() && !node.isExpanded()) {
126109             view.expand(node);
126110             this.expandProcId = false;
126111         }
126112     },
126113
126114     // private
126115     queueExpand : function(node) {
126116         this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]);
126117     },
126118
126119     // private
126120     cancelExpand : function() {
126121         if (this.expandProcId) {
126122             clearTimeout(this.expandProcId);
126123             this.expandProcId = false;
126124         }
126125     },
126126
126127     getPosition: function(e, node) {
126128         var view = this.view,
126129             record = view.getRecord(node),
126130             y = e.getPageY(),
126131             noAppend = record.isLeaf(),
126132             noBelow = false,
126133             region = Ext.fly(node).getRegion(),
126134             fragment;
126135
126136         // If we are dragging on top of the root node of the tree, we always want to append.
126137         if (record.isRoot()) {
126138             return 'append';
126139         }
126140
126141         // Return 'append' if the node we are dragging on top of is not a leaf else return false.
126142         if (this.appendOnly) {
126143             return noAppend ? false : 'append';
126144         }
126145
126146         if (!this.allowParentInsert) {
126147             noBelow = record.hasChildNodes() && record.isExpanded();
126148         }
126149
126150         fragment = (region.bottom - region.top) / (noAppend ? 2 : 3);
126151         if (y >= region.top && y < (region.top + fragment)) {
126152             return 'before';
126153         }
126154         else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) {
126155             return 'after';
126156         }
126157         else {
126158             return 'append';
126159         }
126160     },
126161
126162     isValidDropPoint : function(node, position, dragZone, e, data) {
126163         if (!node || !data.item) {
126164             return false;
126165         }
126166
126167         var view = this.view,
126168             targetNode = view.getRecord(node),
126169             draggedRecords = data.records,
126170             dataLength = draggedRecords.length,
126171             ln = draggedRecords.length,
126172             i, record;
126173
126174         // No drop position, or dragged records: invalid drop point
126175         if (!(targetNode && position && dataLength)) {
126176             return false;
126177         }
126178
126179         // If the targetNode is within the folder we are dragging
126180         for (i = 0; i < ln; i++) {
126181             record = draggedRecords[i];
126182             if (record.isNode && record.contains(targetNode)) {
126183                 return false;
126184             }
126185         }
126186         
126187         // Respect the allowDrop field on Tree nodes
126188         if (position === 'append' && targetNode.get('allowDrop') == false) {
126189             return false;
126190         }
126191         else if (position != 'append' && targetNode.parentNode.get('allowDrop') == false) {
126192             return false;
126193         }
126194
126195         // If the target record is in the dragged dataset, then invalid drop
126196         if (Ext.Array.contains(draggedRecords, targetNode)) {
126197              return false;
126198         }
126199
126200         // @TODO: fire some event to notify that there is a valid drop possible for the node you're dragging
126201         // Yes: this.fireViewEvent(blah....) fires an event through the owning View.
126202         return true;
126203     },
126204
126205     onNodeOver : function(node, dragZone, e, data) {
126206         var position = this.getPosition(e, node),
126207             returnCls = this.dropNotAllowed,
126208             view = this.view,
126209             targetNode = view.getRecord(node),
126210             indicator = this.getIndicator(),
126211             indicatorX = 0,
126212             indicatorY = 0;
126213
126214         // auto node expand check
126215         this.cancelExpand();
126216         if (position == 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) {
126217             this.queueExpand(targetNode);
126218         }
126219             
126220         if (this.isValidDropPoint(node, position, dragZone, e, data)) {
126221             this.valid = true;
126222             this.currentPosition = position;
126223             this.overRecord = targetNode;
126224
126225             indicator.setWidth(Ext.fly(node).getWidth());
126226             indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1;
126227
126228             if (position == 'before') {
126229                 returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
126230                 indicator.showAt(0, indicatorY);
126231                 indicator.toFront();
126232             }
126233             else if (position == 'after') {
126234                 returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
126235                 indicatorY += Ext.fly(node).getHeight();
126236                 indicator.showAt(0, indicatorY);
126237                 indicator.toFront();
126238             }
126239             else {
126240                 returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append';
126241                 // @TODO: set a class on the parent folder node to be able to style it
126242                 indicator.hide();
126243             }
126244         }
126245         else {
126246             this.valid = false;
126247         }
126248
126249         this.currentCls = returnCls;
126250         return returnCls;
126251     },
126252
126253     onContainerOver : function(dd, e, data) {
126254         return e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed;
126255     },
126256     
126257     notifyOut: function() {
126258         this.callParent(arguments);
126259         this.cancelExpand();
126260     },
126261
126262     handleNodeDrop : function(data, targetNode, position) {
126263         var me = this,
126264             view = me.view,
126265             parentNode = targetNode.parentNode,
126266             store = view.getStore(),
126267             recordDomNodes = [],
126268             records, i, len,
126269             insertionMethod, argList,
126270             needTargetExpand,
126271             transferData,
126272             processDrop;
126273
126274         // If the copy flag is set, create a copy of the Models with the same IDs
126275         if (data.copy) {
126276             records = data.records;
126277             data.records = [];
126278             for (i = 0, len = records.length; i < len; i++) {
126279                 data.records.push(Ext.apply({}, records[i].data));
126280             }
126281         }
126282
126283         // Cancel any pending expand operation
126284         me.cancelExpand();
126285
126286         // Grab a reference to the correct node insertion method.
126287         // Create an arg list array intended for the apply method of the
126288         // chosen node insertion method.
126289         // Ensure the target object for the method is referenced by 'targetNode'
126290         if (position == 'before') {
126291             insertionMethod = parentNode.insertBefore;
126292             argList = [null, targetNode];
126293             targetNode = parentNode;
126294         }
126295         else if (position == 'after') {
126296             if (targetNode.nextSibling) {
126297                 insertionMethod = parentNode.insertBefore;
126298                 argList = [null, targetNode.nextSibling];
126299             }
126300             else {
126301                 insertionMethod = parentNode.appendChild;
126302                 argList = [null];
126303             }
126304             targetNode = parentNode;
126305         }
126306         else {
126307             if (!targetNode.isExpanded()) {
126308                 needTargetExpand = true;
126309             }
126310             insertionMethod = targetNode.appendChild;
126311             argList = [null];
126312         }
126313
126314         // A function to transfer the data into the destination tree
126315         transferData = function() {
126316             var node;
126317             for (i = 0, len = data.records.length; i < len; i++) {
126318                 argList[0] = data.records[i];
126319                 node = insertionMethod.apply(targetNode, argList);
126320                 
126321                 if (Ext.enableFx && me.dropHighlight) {
126322                     recordDomNodes.push(view.getNode(node));
126323                 }
126324             }
126325             
126326             // Kick off highlights after everything's been inserted, so they are
126327             // more in sync without insertion/render overhead.
126328             if (Ext.enableFx && me.dropHighlight) {
126329                 //FIXME: the check for n.firstChild is not a great solution here. Ideally the line should simply read 
126330                 //Ext.fly(n.firstChild) but this yields errors in IE6 and 7. See ticket EXTJSIV-1705 for more details
126331                 Ext.Array.forEach(recordDomNodes, function(n) {
126332                     Ext.fly(n.firstChild ? n.firstChild : n).highlight(me.dropHighlightColor);
126333                 });
126334             }
126335         };
126336
126337         // If dropping right on an unexpanded node, transfer the data after it is expanded.
126338         if (needTargetExpand) {
126339             targetNode.expand(false, transferData);
126340         }
126341         // Otherwise, call the data transfer function immediately
126342         else {
126343             transferData();
126344         }
126345     }
126346 });
126347 /**
126348  * @class Ext.tree.ViewDDPlugin
126349  * @extends Ext.AbstractPlugin
126350  * <p>This plugin provides drag and/or drop functionality for a TreeView.</p>
126351  * <p>It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a {@link Ext.tree.View TreeView}
126352  * and loads the data object which is passed to a cooperating {@link Ext.dd.DragZone DragZone}'s methods with the following properties:<ul>
126353  * <li>copy : Boolean
126354  *  <div class="sub-desc">The value of the TreeView's <code>copy</code> property, or <code>true</code> if the TreeView was configured
126355  *  with <code>allowCopy: true</code> <u>and</u> the control key was pressed when the drag operation was begun.</div></li>
126356  * <li>view : TreeView
126357  *  <div class="sub-desc">The source TreeView from which the drag originated.</div></li>
126358  * <li>ddel : HtmlElement
126359  *  <div class="sub-desc">The drag proxy element which moves with the mouse</div></li>
126360  * <li>item : HtmlElement
126361  *  <div class="sub-desc">The TreeView node upon which the mousedown event was registered.</div></li>
126362  * <li>records : Array
126363  *  <div class="sub-desc">An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source TreeView.</div></li>
126364  * </ul></p>
126365  * <p>It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are members of the same
126366  * ddGroup which processes such data objects.</p>
126367  * <p>Adding this plugin to a view means that two new events may be fired from the client TreeView, <code>{@link #event-beforedrop beforedrop}</code> and
126368  * <code>{@link #event-drop drop}</code></p>
126369  */
126370 Ext.define('Ext.tree.plugin.TreeViewDragDrop', {
126371     extend: 'Ext.AbstractPlugin',
126372     alias: 'plugin.treeviewdragdrop',
126373
126374     uses: [
126375         'Ext.tree.ViewDragZone',
126376         'Ext.tree.ViewDropZone'
126377     ],
126378
126379     /**
126380      * @event beforedrop
126381      * <p><b>This event is fired through the TreeView. Add listeners to the TreeView object</b></p>
126382      * <p>Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the TreeView.
126383      * @param {HtmlElement} node The TreeView node <b>if any</b> over which the mouse was positioned.</p>
126384      * <p>Returning <code>false</code> to this event signals that the drop gesture was invalid, and if the drag proxy
126385      * will animate back to the point from which the drag began.</p>
126386      * <p>Returning <code>0</code> To this event signals that the data transfer operation should not take place, but
126387      * that the gesture was valid, and that the repair operation should not take place.</p>
126388      * <p>Any other return value continues with the data transfer operation.</p>
126389      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone DragZone}'s
126390      * {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:<ul>
126391      * <li>copy : Boolean
126392      *  <div class="sub-desc">The value of the TreeView's <code>copy</code> property, or <code>true</code> if the TreeView was configured
126393      *  with <code>allowCopy: true</code> and the control key was pressed when the drag operation was begun</div></li>
126394      * <li>view : TreeView
126395      *  <div class="sub-desc">The source TreeView from which the drag originated.</div></li>
126396      * <li>ddel : HtmlElement
126397      *  <div class="sub-desc">The drag proxy element which moves with the mouse</div></li>
126398      * <li>item : HtmlElement
126399      *  <div class="sub-desc">The TreeView node upon which the mousedown event was registered.</div></li>
126400      * <li>records : Array
126401      *  <div class="sub-desc">An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source TreeView.</div></li>
126402      * </ul>
126403      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
126404      * @param {String} dropPosition <code>"before"</code>, <code>"after"</code> or <code>"append"</code> depending on whether the mouse is above or below the midline of the node,
126405      * or the node is a branch node which accepts new child nodes.
126406      * @param {Function} dropFunction <p>A function to call to complete the data transfer operation and either move or copy Model instances from the source
126407      * View's Store to the destination View's Store.</p>
126408      * <p>This is useful when you want to perform some kind of asynchronous processing before confirming
126409      * the drop, such as an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.</p>
126410      * <p>Return <code>0</code> from this event handler, and call the <code>dropFunction</code> at any time to perform the data transfer.</p>
126411      */
126412
126413     /**
126414      * @event drop
126415      * <b>This event is fired through the TreeView. Add listeners to the TreeView object</b>
126416      * Fired when a drop operation has been completed and the data has been moved or copied.
126417      * @param {HtmlElement} node The TreeView node <b>if any</b> over which the mouse was positioned.
126418      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone DragZone}'s
126419      * {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:<ul>
126420      * <li>copy : Boolean
126421      *  <div class="sub-desc">The value of the TreeView's <code>copy</code> property, or <code>true</code> if the TreeView was configured
126422      *  with <code>allowCopy: true</code> and the control key was pressed when the drag operation was begun</div></li>
126423      * <li>view : TreeView
126424      *  <div class="sub-desc">The source TreeView from which the drag originated.</div></li>
126425      * <li>ddel : HtmlElement
126426      *  <div class="sub-desc">The drag proxy element which moves with the mouse</div></li>
126427      * <li>item : HtmlElement
126428      *  <div class="sub-desc">The TreeView node upon which the mousedown event was registered.</div></li>
126429      * <li>records : Array
126430      *  <div class="sub-desc">An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source TreeView.</div></li>
126431      * </ul>
126432      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
126433      * @param {String} dropPosition <code>"before"</code>, <code>"after"</code> or <code>"append"</code> depending on whether the mouse is above or below the midline of the node,
126434      * or the node is a branch node which accepts new child nodes.
126435      */
126436
126437     dragText : '{0} selected node{1}',
126438
126439     /**
126440      * @cfg {Boolean} allowParentInsert
126441      * Allow inserting a dragged node between an expanded parent node and its first child that will become a
126442      * sibling of the parent when dropped (defaults to false)
126443      */
126444     allowParentInserts: false,
126445
126446     /**
126447      * @cfg {String} allowContainerDrop
126448      * True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false)
126449      */
126450     allowContainerDrops: false,
126451
126452     /**
126453      * @cfg {String} appendOnly
126454      * True if the tree should only allow append drops (use for trees which are sorted, defaults to false)
126455      */
126456     appendOnly: false,
126457
126458     /**
126459      * @cfg {String} ddGroup
126460      * A named drag drop group to which this object belongs.  If a group is specified, then both the DragZones and DropZone
126461      * used by this plugin will only interact with other drag drop objects in the same group (defaults to 'TreeDD').
126462      */
126463     ddGroup : "TreeDD",
126464
126465     /**
126466      * @cfg {String} dragGroup
126467      * <p>The ddGroup to which the DragZone will belong.</p>
126468      * <p>This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other Drag/DropZones
126469      * which are members of the same ddGroup.</p>
126470      */
126471
126472     /**
126473      * @cfg {String} dropGroup
126474      * <p>The ddGroup to which the DropZone will belong.</p>
126475      * <p>This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other Drag/DropZones
126476      * which are members of the same ddGroup.</p>
126477      */
126478
126479     /**
126480      * @cfg {String} expandDelay
126481      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
126482      * over the target (defaults to 1000)
126483      */
126484     expandDelay : 1000,
126485
126486     /**
126487      * @cfg {Boolean} enableDrop
126488      * <p>Defaults to <code>true</code></p>
126489      * <p>Set to <code>false</code> to disallow the View from accepting drop gestures</p>
126490      */
126491     enableDrop: true,
126492
126493     /**
126494      * @cfg {Boolean} enableDrag
126495      * <p>Defaults to <code>true</code></p>
126496      * <p>Set to <code>false</code> to disallow dragging items from the View </p>
126497      */
126498     enableDrag: true,
126499     
126500     /**
126501      * @cfg {String} nodeHighlightColor The color to use when visually highlighting the dragged
126502      * or dropped node (defaults to 'c3daf9' - light blue). The color must be a 6 digit hex value, without
126503      * a preceding '#'. See also {@link #nodeHighlightOnDrop} and {@link #nodeHighlightOnRepair}.
126504      */
126505     nodeHighlightColor: 'c3daf9',
126506     
126507     /**
126508      * @cfg {Boolean} nodeHighlightOnDrop Whether or not to highlight any nodes after they are
126509      * successfully dropped on their target. Defaults to the value of `Ext.enableFx`.
126510      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnRepair}.
126511      * @markdown
126512      */
126513     nodeHighlightOnDrop: Ext.enableFx,
126514     
126515     /**
126516      * @cfg {Boolean} nodeHighlightOnRepair Whether or not to highlight any nodes after they are
126517      * repaired from an unsuccessful drag/drop. Defaults to the value of `Ext.enableFx`.
126518      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnDrop}.
126519      * @markdown
126520      */
126521     nodeHighlightOnRepair: Ext.enableFx,
126522
126523     init : function(view) {
126524         view.on('render', this.onViewRender, this, {single: true});
126525     },
126526
126527     /**
126528      * @private
126529      * AbstractComponent calls destroy on all its plugins at destroy time.
126530      */
126531     destroy: function() {
126532         Ext.destroy(this.dragZone, this.dropZone);
126533     },
126534
126535     onViewRender : function(view) {
126536         var me = this;
126537
126538         if (me.enableDrag) {
126539             me.dragZone = Ext.create('Ext.tree.ViewDragZone', {
126540                 view: view,
126541                 ddGroup: me.dragGroup || me.ddGroup,
126542                 dragText: me.dragText,
126543                 repairHighlightColor: me.nodeHighlightColor,
126544                 repairHighlight: me.nodeHighlightOnRepair
126545             });
126546         }
126547
126548         if (me.enableDrop) {
126549             me.dropZone = Ext.create('Ext.tree.ViewDropZone', {
126550                 view: view,
126551                 ddGroup: me.dropGroup || me.ddGroup,
126552                 allowContainerDrops: me.allowContainerDrops,
126553                 appendOnly: me.appendOnly,
126554                 allowParentInserts: me.allowParentInserts,
126555                 expandDelay: me.expandDelay,
126556                 dropHighlightColor: me.nodeHighlightColor,
126557                 dropHighlight: me.nodeHighlightOnDrop
126558             });
126559         }
126560     }
126561 });
126562 /**
126563  * @class Ext.util.Cookies
126564
126565 Utility class for setting/reading values from browser cookies.
126566 Values can be written using the {@link #set} method.
126567 Values can be read using the {@link #get} method.
126568 A cookie can be invalidated on the client machine using the {@link #clear} method.
126569
126570  * @markdown
126571  * @singleton
126572  */
126573 Ext.define('Ext.util.Cookies', {
126574     singleton: true,
126575     
126576     /**
126577      * Create a cookie with the specified name and value. Additional settings
126578      * for the cookie may be optionally specified (for example: expiration,
126579      * access restriction, SSL).
126580      * @param {String} name The name of the cookie to set. 
126581      * @param {Mixed} value The value to set for the cookie.
126582      * @param {Object} expires (Optional) Specify an expiration date the
126583      * cookie is to persist until.  Note that the specified Date object will
126584      * be converted to Greenwich Mean Time (GMT). 
126585      * @param {String} path (Optional) Setting a path on the cookie restricts
126586      * access to pages that match that path. Defaults to all pages (<tt>'/'</tt>). 
126587      * @param {String} domain (Optional) Setting a domain restricts access to
126588      * pages on a given domain (typically used to allow cookie access across
126589      * subdomains). For example, "sencha.com" will create a cookie that can be
126590      * accessed from any subdomain of sencha.com, including www.sencha.com,
126591      * support.sencha.com, etc.
126592      * @param {Boolean} secure (Optional) Specify true to indicate that the cookie
126593      * should only be accessible via SSL on a page using the HTTPS protocol.
126594      * Defaults to <tt>false</tt>. Note that this will only work if the page
126595      * calling this code uses the HTTPS protocol, otherwise the cookie will be
126596      * created with default options.
126597      */
126598     set : function(name, value){
126599         var argv = arguments,
126600             argc = arguments.length,
126601             expires = (argc > 2) ? argv[2] : null,
126602             path = (argc > 3) ? argv[3] : '/',
126603             domain = (argc > 4) ? argv[4] : null,
126604             secure = (argc > 5) ? argv[5] : false;
126605             
126606         document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
126607     },
126608
126609     /**
126610      * Retrieves cookies that are accessible by the current page. If a cookie
126611      * does not exist, <code>get()</code> returns <tt>null</tt>.  The following
126612      * example retrieves the cookie called "valid" and stores the String value
126613      * in the variable <tt>validStatus</tt>.
126614      * <pre><code>
126615      * var validStatus = Ext.util.Cookies.get("valid");
126616      * </code></pre>
126617      * @param {String} name The name of the cookie to get
126618      * @return {Mixed} Returns the cookie value for the specified name;
126619      * null if the cookie name does not exist.
126620      */
126621     get : function(name){
126622         var arg = name + "=",
126623             alen = arg.length,
126624             clen = document.cookie.length,
126625             i = 0,
126626             j = 0;
126627             
126628         while(i < clen){
126629             j = i + alen;
126630             if(document.cookie.substring(i, j) == arg){
126631                 return this.getCookieVal(j);
126632             }
126633             i = document.cookie.indexOf(" ", i) + 1;
126634             if(i === 0){
126635                 break;
126636             }
126637         }
126638         return null;
126639     },
126640
126641     /**
126642      * Removes a cookie with the provided name from the browser
126643      * if found by setting its expiration date to sometime in the past. 
126644      * @param {String} name The name of the cookie to remove
126645      * @param {String} path (optional) The path for the cookie. This must be included if you included a path while setting the cookie.
126646      */
126647     clear : function(name, path){
126648         if(this.get(name)){
126649             path = path || '/';
126650             document.cookie = name + '=' + '; expires=Thu, 01-Jan-70 00:00:01 GMT; path=' + path;
126651         }
126652     },
126653     
126654     /**
126655      * @private
126656      */
126657     getCookieVal : function(offset){
126658         var endstr = document.cookie.indexOf(";", offset);
126659         if(endstr == -1){
126660             endstr = document.cookie.length;
126661         }
126662         return unescape(document.cookie.substring(offset, endstr));
126663     }
126664 });
126665
126666 /**
126667  * @class Ext.util.CSS
126668  * Utility class for manipulating CSS rules
126669  * @singleton
126670  */
126671 Ext.define('Ext.util.CSS', function() {
126672     var rules = null;
126673     var doc = document;
126674
126675     var camelRe = /(-[a-z])/gi;
126676     var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
126677
126678     return {
126679
126680         singleton: true,
126681
126682         constructor: function() {
126683             this.rules = {};
126684             this.initialized = false;
126685         },
126686  
126687         /**
126688          * Creates a stylesheet from a text blob of rules.
126689          * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
126690          * @param {String} cssText The text containing the css rules
126691          * @param {String} id An id to add to the stylesheet for later removal
126692          * @return {StyleSheet}
126693          */
126694         createStyleSheet : function(cssText, id) {
126695             var ss,
126696                 head = doc.getElementsByTagName("head")[0],
126697                 styleEl = doc.createElement("style");
126698
126699             styleEl.setAttribute("type", "text/css");
126700             if (id) {
126701                styleEl.setAttribute("id", id);
126702             }
126703
126704             if (Ext.isIE) {
126705                head.appendChild(styleEl);
126706                ss = styleEl.styleSheet;
126707                ss.cssText = cssText;
126708             } else {
126709                 try{
126710                     styleEl.appendChild(doc.createTextNode(cssText));
126711                 } catch(e) {
126712                    styleEl.cssText = cssText;
126713                 }
126714                 head.appendChild(styleEl);
126715                 ss = styleEl.styleSheet ? styleEl.styleSheet : (styleEl.sheet || doc.styleSheets[doc.styleSheets.length-1]);
126716             }
126717             this.cacheStyleSheet(ss);
126718             return ss;
126719         },
126720
126721         /**
126722          * Removes a style or link tag by id
126723          * @param {String} id The id of the tag
126724          */
126725         removeStyleSheet : function(id) {
126726             var existing = document.getElementById(id);
126727             if (existing) {
126728                 existing.parentNode.removeChild(existing);
126729             }
126730         },
126731
126732         /**
126733          * Dynamically swaps an existing stylesheet reference for a new one
126734          * @param {String} id The id of an existing link tag to remove
126735          * @param {String} url The href of the new stylesheet to include
126736          */
126737         swapStyleSheet : function(id, url) {
126738             var doc = document;
126739             this.removeStyleSheet(id);
126740             var ss = doc.createElement("link");
126741             ss.setAttribute("rel", "stylesheet");
126742             ss.setAttribute("type", "text/css");
126743             ss.setAttribute("id", id);
126744             ss.setAttribute("href", url);
126745             doc.getElementsByTagName("head")[0].appendChild(ss);
126746         },
126747
126748         /**
126749          * Refresh the rule cache if you have dynamically added stylesheets
126750          * @return {Object} An object (hash) of rules indexed by selector
126751          */
126752         refreshCache : function() {
126753             return this.getRules(true);
126754         },
126755
126756         // private
126757         cacheStyleSheet : function(ss) {
126758             if(!rules){
126759                 rules = {};
126760             }
126761             try {// try catch for cross domain access issue
126762                 var ssRules = ss.cssRules || ss.rules,
126763                     selectorText,
126764                     i = ssRules.length - 1,
126765                     j,
126766                     selectors;
126767
126768                 for (; i >= 0; --i) {
126769                     selectorText = ssRules[i].selectorText;
126770                     if (selectorText) {
126771  
126772                         // Split in case there are multiple, comma-delimited selectors
126773                         selectorText = selectorText.split(',');
126774                         selectors = selectorText.length;
126775                         for (j = 0; j < selectors; j++) {
126776                             rules[Ext.String.trim(selectorText[j]).toLowerCase()] = ssRules[i];
126777                         }
126778                     }
126779                 }
126780             } catch(e) {}
126781         },
126782
126783         /**
126784         * Gets all css rules for the document
126785         * @param {Boolean} refreshCache true to refresh the internal cache
126786         * @return {Object} An object (hash) of rules indexed by selector
126787         */
126788         getRules : function(refreshCache) {
126789             if (rules === null || refreshCache) {
126790                 rules = {};
126791                 var ds = doc.styleSheets,
126792                     i = 0,
126793                     len = ds.length;
126794
126795                 for (; i < len; i++) {
126796                     try {
126797                         if (!ds[i].disabled) {
126798                             this.cacheStyleSheet(ds[i]);
126799                         }
126800                     } catch(e) {} 
126801                 }
126802             }
126803             return rules;
126804         },
126805
126806         /**
126807          * Gets an an individual CSS rule by selector(s)
126808          * @param {String/Array} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
126809          * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
126810          * @return {CSSRule} The CSS rule or null if one is not found
126811          */
126812         getRule: function(selector, refreshCache) {
126813             var rs = this.getRules(refreshCache);
126814             if (!Ext.isArray(selector)) {
126815                 return rs[selector.toLowerCase()];
126816             }
126817             for (var i = 0; i < selector.length; i++) {
126818                 if (rs[selector[i]]) {
126819                     return rs[selector[i].toLowerCase()];
126820                 }
126821             }
126822             return null;
126823         },
126824
126825         /**
126826          * Updates a rule property
126827          * @param {String/Array} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
126828          * @param {String} property The css property
126829          * @param {String} value The new value for the property
126830          * @return {Boolean} true If a rule was found and updated
126831          */
126832         updateRule : function(selector, property, value){
126833             if (!Ext.isArray(selector)) {
126834                 var rule = this.getRule(selector);
126835                 if (rule) {
126836                     rule.style[property.replace(camelRe, camelFn)] = value;
126837                     return true;
126838                 }
126839             } else {
126840                 for (var i = 0; i < selector.length; i++) {
126841                     if (this.updateRule(selector[i], property, value)) {
126842                         return true;
126843                     }
126844                 }
126845             }
126846             return false;
126847         }
126848     };
126849 }());
126850 /**
126851  * @class Ext.util.History
126852
126853 History management component that allows you to register arbitrary tokens that signify application
126854 history state on navigation actions.  You can then handle the history {@link #change} event in order
126855 to reset your application UI to the appropriate state when the user navigates forward or backward through
126856 the browser history stack.
126857
126858 __Initializing__
126859 The {@link #init} method of the History object must be called before using History. This sets up the internal
126860 state and must be the first thing called before using History.
126861
126862 __Setup__
126863 The History objects requires elements on the page to keep track of the browser history. For older versions of IE,
126864 an IFrame is required to do the tracking. For other browsers, a hidden field can be used. The history objects expects
126865 these to be on the page before the {@link #init} method is called. The following markup is suggested in order
126866 to support all browsers:
126867
126868     <form id="history-form" class="x-hide-display">
126869         <input type="hidden" id="x-history-field" />
126870         <iframe id="x-history-frame"></iframe>
126871     </form>
126872
126873  * @markdown
126874  * @singleton
126875  */
126876 Ext.define('Ext.util.History', {
126877     singleton: true,
126878     alternateClassName: 'Ext.History',
126879     mixins: {
126880         observable: 'Ext.util.Observable'
126881     },
126882     
126883     constructor: function() {
126884         var me = this;
126885         me.oldIEMode = Ext.isIE6 || Ext.isIE7 || !Ext.isStrict && Ext.isIE8;
126886         me.iframe = null;
126887         me.hiddenField = null;
126888         me.ready = false;
126889         me.currentToken = null;
126890     },
126891     
126892     getHash: function() {
126893         var href = window.location.href,
126894             i = href.indexOf("#");
126895             
126896         return i >= 0 ? href.substr(i + 1) : null;
126897     },
126898
126899     doSave: function() {
126900         this.hiddenField.value = this.currentToken;
126901     },
126902     
126903
126904     handleStateChange: function(token) {
126905         this.currentToken = token;
126906         this.fireEvent('change', token);
126907     },
126908
126909     updateIFrame: function(token) {
126910         var html = '<html><body><div id="state">' + 
126911                     Ext.util.Format.htmlEncode(token) + 
126912                     '</div></body></html>';
126913
126914         try {
126915             var doc = this.iframe.contentWindow.document;
126916             doc.open();
126917             doc.write(html);
126918             doc.close();
126919             return true;
126920         } catch (e) {
126921             return false;
126922         }
126923     },
126924
126925     checkIFrame: function () {
126926         var me = this,
126927             contentWindow = me.iframe.contentWindow;
126928             
126929         if (!contentWindow || !contentWindow.document) {
126930             Ext.Function.defer(this.checkIFrame, 10, this);
126931             return;
126932         }
126933        
126934         var doc = contentWindow.document,
126935             elem = doc.getElementById("state"),
126936             oldToken = elem ? elem.innerText : null,
126937             oldHash = me.getHash();
126938            
126939         Ext.TaskManager.start({
126940             run: function () {
126941                 var doc = contentWindow.document,
126942                     elem = doc.getElementById("state"),
126943                     newToken = elem ? elem.innerText : null,
126944                     newHash = me.getHash();
126945
126946                 if (newToken !== oldToken) {
126947                     oldToken = newToken;
126948                     me.handleStateChange(newToken);
126949                     window.top.location.hash = newToken;
126950                     oldHash = newToken;
126951                     me.doSave();
126952                 } else if (newHash !== oldHash) {
126953                     oldHash = newHash;
126954                     me.updateIFrame(newHash);
126955                 }
126956             }, 
126957             interval: 50,
126958             scope: me
126959         });
126960         me.ready = true;
126961         me.fireEvent('ready', me);            
126962     },
126963
126964     startUp: function () {
126965         var me = this;
126966         
126967         me.currentToken = me.hiddenField.value || this.getHash();
126968
126969         if (me.oldIEMode) {
126970             me.checkIFrame();
126971         } else {
126972             var hash = me.getHash();
126973             Ext.TaskManager.start({
126974                 run: function () {
126975                     var newHash = me.getHash();
126976                     if (newHash !== hash) {
126977                         hash = newHash;
126978                         me.handleStateChange(hash);
126979                         me.doSave();
126980                     }
126981                 },
126982                 interval: 50,
126983                 scope: me
126984             });
126985             me.ready = true;
126986             me.fireEvent('ready', me);
126987         }
126988         
126989     },
126990
126991     /**
126992      * The id of the hidden field required for storing the current history token.
126993      * @type String
126994      * @property
126995      */
126996     fieldId: Ext.baseCSSPrefix + 'history-field',
126997     /**
126998      * The id of the iframe required by IE to manage the history stack.
126999      * @type String
127000      * @property
127001      */
127002     iframeId: Ext.baseCSSPrefix + 'history-frame',
127003
127004     /**
127005      * Initialize the global History instance.
127006      * @param {Boolean} onReady (optional) A callback function that will be called once the history
127007      * component is fully initialized.
127008      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser window.
127009      */
127010     init: function (onReady, scope) {
127011         var me = this;
127012         
127013         if (me.ready) {
127014             Ext.callback(onReady, scope, [me]);
127015             return;
127016         }
127017         
127018         if (!Ext.isReady) {
127019             Ext.onReady(function() {
127020                 me.init(onReady, scope);
127021             });
127022             return;
127023         }
127024         
127025         me.hiddenField = Ext.getDom(me.fieldId);
127026         
127027         if (me.oldIEMode) {
127028             me.iframe = Ext.getDom(me.iframeId);
127029         }
127030         
127031         me.addEvents(
127032             /**
127033              * @event ready
127034              * Fires when the Ext.util.History singleton has been initialized and is ready for use.
127035              * @param {Ext.util.History} The Ext.util.History singleton.
127036              */
127037             'ready',
127038             /**
127039              * @event change
127040              * Fires when navigation back or forwards within the local page's history occurs.
127041              * @param {String} token An identifier associated with the page state at that point in its history.
127042              */
127043             'change'
127044         );
127045         
127046         if (onReady) {
127047             me.on('ready', onReady, scope, {single: true});
127048         }
127049         me.startUp();
127050     },
127051
127052     /**
127053      * Add a new token to the history stack. This can be any arbitrary value, although it would
127054      * commonly be the concatenation of a component id and another id marking the specifc history
127055      * state of that component.  Example usage:
127056      * <pre><code>
127057 // Handle tab changes on a TabPanel
127058 tabPanel.on('tabchange', function(tabPanel, tab){
127059 Ext.History.add(tabPanel.id + ':' + tab.id);
127060 });
127061 </code></pre>
127062      * @param {String} token The value that defines a particular application-specific history state
127063      * @param {Boolean} preventDuplicates When true, if the passed token matches the current token
127064      * it will not save a new history step. Set to false if the same state can be saved more than once
127065      * at the same history stack location (defaults to true).
127066      */
127067     add: function (token, preventDup) {
127068         var me = this;
127069         
127070         if (preventDup !== false) {
127071             if (me.getToken() === token) {
127072                 return true;
127073             }
127074         }
127075         
127076         if (me.oldIEMode) {
127077             return me.updateIFrame(token);
127078         } else {
127079             window.top.location.hash = token;
127080             return true;
127081         }
127082     },
127083
127084     /**
127085      * Programmatically steps back one step in browser history (equivalent to the user pressing the Back button).
127086      */
127087     back: function() {
127088         window.history.go(-1);
127089     },
127090
127091     /**
127092      * Programmatically steps forward one step in browser history (equivalent to the user pressing the Forward button).
127093      */
127094     forward: function(){
127095         window.history.go(1);
127096     },
127097
127098     /**
127099      * Retrieves the currently-active history token.
127100      * @return {String} The token
127101      */
127102     getToken: function() {
127103         return this.ready ? this.currentToken : this.getHash();
127104     }
127105 });
127106 /**
127107  * @class Ext.view.TableChunker
127108  * 
127109  * Produces optimized XTemplates for chunks of tables to be
127110  * used in grids, trees and other table based widgets.
127111  *
127112  * @singleton
127113  */
127114 Ext.define('Ext.view.TableChunker', {
127115     singleton: true,
127116     requires: ['Ext.XTemplate'],
127117     metaTableTpl: [
127118         '{[this.openTableWrap()]}',
127119         '<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" border="0" cellspacing="0" cellpadding="0" {[this.embedFullWidth()]}>',
127120             '<tbody>',
127121             '<tr>',
127122             '<tpl for="columns">',
127123                 '<th class="' + Ext.baseCSSPrefix + 'grid-col-resizer-{id}" style="width: {width}px; height: 0px;"></th>',
127124             '</tpl>',
127125             '</tr>',
127126             '{[this.openRows()]}',
127127                 '{row}',
127128                 '<tpl for="features">',
127129                     '{[this.embedFeature(values, parent, xindex, xcount)]}',
127130                 '</tpl>',
127131             '{[this.closeRows()]}',
127132             '</tbody>',
127133         '</table>',
127134         '{[this.closeTableWrap()]}'
127135     ],
127136
127137     constructor: function() {
127138         Ext.XTemplate.prototype.recurse = function(values, reference) {
127139             return this.apply(reference ? values[reference] : values);
127140         };
127141     },
127142
127143     embedFeature: function(values, parent, x, xcount) {
127144         var tpl = '';
127145         if (!values.disabled) {
127146             tpl = values.getFeatureTpl(values, parent, x, xcount);
127147         }
127148         return tpl;
127149     },
127150
127151     embedFullWidth: function() {
127152         return 'style="width: {fullWidth}px;"';
127153     },
127154
127155     openRows: function() {
127156         return '<tpl for="rows">';
127157     },
127158
127159     closeRows: function() {
127160         return '</tpl>';
127161     },
127162
127163     metaRowTpl: [
127164         '<tr class="' + Ext.baseCSSPrefix + 'grid-row {addlSelector} {[this.embedRowCls()]}" {[this.embedRowAttr()]}>',
127165             '<tpl for="columns">',
127166                 '<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>',
127167             '</tpl>',
127168         '</tr>'
127169     ],
127170     
127171     firstOrLastCls: function(xindex, xcount) {
127172         var cssCls = '';
127173         if (xindex === 1) {
127174             cssCls = Ext.baseCSSPrefix + 'grid-cell-first';
127175         } else if (xindex === xcount) {
127176             cssCls = Ext.baseCSSPrefix + 'grid-cell-last';
127177         }
127178         return cssCls;
127179     },
127180     
127181     embedRowCls: function() {
127182         return '{rowCls}';
127183     },
127184     
127185     embedRowAttr: function() {
127186         return '{rowAttr}';
127187     },
127188     
127189     openTableWrap: function() {
127190         return '';
127191     },
127192     
127193     closeTableWrap: function() {
127194         return '';
127195     },
127196
127197     getTableTpl: function(cfg, textOnly) {
127198         var tpl,
127199             tableTplMemberFns = {
127200                 openRows: this.openRows,
127201                 closeRows: this.closeRows,
127202                 embedFeature: this.embedFeature,
127203                 embedFullWidth: this.embedFullWidth,
127204                 openTableWrap: this.openTableWrap,
127205                 closeTableWrap: this.closeTableWrap
127206             },
127207             tplMemberFns = {},
127208             features = cfg.features || [],
127209             ln = features.length,
127210             i  = 0,
127211             memberFns = {
127212                 embedRowCls: this.embedRowCls,
127213                 embedRowAttr: this.embedRowAttr,
127214                 firstOrLastCls: this.firstOrLastCls
127215             },
127216             // copy the default
127217             metaRowTpl = Array.prototype.slice.call(this.metaRowTpl, 0),
127218             metaTableTpl;
127219             
127220         for (; i < ln; i++) {
127221             if (!features[i].disabled) {
127222                 features[i].mutateMetaRowTpl(metaRowTpl);
127223                 Ext.apply(memberFns, features[i].getMetaRowTplFragments());
127224                 Ext.apply(tplMemberFns, features[i].getFragmentTpl());
127225                 Ext.apply(tableTplMemberFns, features[i].getTableFragments());
127226             }
127227         }
127228         
127229         metaRowTpl = Ext.create('Ext.XTemplate', metaRowTpl.join(''), memberFns);
127230         cfg.row = metaRowTpl.applyTemplate(cfg);
127231         
127232         metaTableTpl = Ext.create('Ext.XTemplate', this.metaTableTpl.join(''), tableTplMemberFns);
127233         
127234         tpl = metaTableTpl.applyTemplate(cfg);
127235         
127236         // TODO: Investigate eliminating.
127237         if (!textOnly) {
127238             tpl = Ext.create('Ext.XTemplate', tpl, tplMemberFns);
127239         }
127240         return tpl;
127241         
127242     }
127243 });
127244
127245
127246
127247 })(this.Ext4 || (this.Ext4 = {}));
127248
127249